Skip to content

Commit dfb36f0

Browse files
authored
Make CupertinoSheetRoute usable with Cupertino(Sliver)NavigationBar (flutter#162181)
Working on the cupertino nav bars recently gave me some context on those widgets, so when I saw this [comment](flutter#157568 (comment)) I was inspired to add a fix :) Thanks @MaherSafadii for [starting the exploration](flutter#157568 (comment)) and also for the very helpful [screenshots](flutter#162021 (comment)). Removes the following when CupertinoNavigationBar/CupertinoSliverNavigationBar is used in a CupertinoSheetRoute: - Unneeded back button - Superfluous top padding in CupertinoNavigationBar - Full page route transitions ## Before: https://github.com/user-attachments/assets/a6da3957-0cff-4491-9380-bbc676ac799d ## After: https://github.com/user-attachments/assets/37cd1628-a47e-44aa-85c7-abceda6e7944 <details> <summary>Sample code</summary> ```dart // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/cupertino.dart'; /// Flutter code sample for [CupertinoSheetRoute]. class CupertinoSheetApp extends StatelessWidget { const CupertinoSheetApp({super.key}); @OverRide Widget build(BuildContext context) { return const CupertinoApp(title: 'Cupertino Sheet', home: HomePage()); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @OverRide Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar( middle: Text('Sheet Example'), automaticBackgroundVisibility: false, ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CupertinoButton.filled( onPressed: () { Navigator.of(context).push( CupertinoSheetRoute<void>( builder: (BuildContext context) => const _SheetScaffold(), ), ); }, child: const Text('Open Bottom Sheet'), ), ], ), ), ); } } class _SheetScaffold extends StatelessWidget { const _SheetScaffold(); @OverRide Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5), middle: const Text('CupertinoNavigationBar Sample'), automaticBackgroundVisibility: false, ), child: Column( children: <Widget>[ Container(height: 50, color: CupertinoColors.systemRed), Container(height: 50, color: CupertinoColors.systemGreen), Container(height: 50, color: CupertinoColors.systemBlue), Container(height: 50, color: CupertinoColors.systemYellow), Center( child: CupertinoButton.filled( onPressed: () { Navigator.of(context).push( CupertinoSheetRoute<void>( builder: (BuildContext context) => const SliverNavBarExample(), ), ); }, child: const Text('Open Bottom Sheet'), ), ) ], ), ); } } class SliverNavBarExample extends StatelessWidget { const SliverNavBarExample({super.key}); @OverRide Widget build(BuildContext context) { return CupertinoPageScaffold( child: CustomScrollView( slivers: <Widget>[ const CupertinoSliverNavigationBar( leading: Icon(CupertinoIcons.person_2), largeTitle: Text('Contacts'), trailing: Icon(CupertinoIcons.add_circled), ), SliverFillRemaining( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ const Text('Drag me up', textAlign: TextAlign.center), CupertinoButton.filled( onPressed: () {}, child: const Text('Bottom Automatic mode'), ), CupertinoButton.filled( onPressed: () {}, child: const Text('Bottom Always mode'), ), ], ), ), ), ], ), ); } } ``` </details> Fixes [Cupertino navbars apply too much top padding within a sheet](flutter#162021). ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
1 parent d3c96c6 commit dfb36f0

File tree

3 files changed

+111
-6
lines changed

3 files changed

+111
-6
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'icons.dart';
2020
import 'page_scaffold.dart';
2121
import 'route.dart';
2222
import 'search_field.dart';
23+
import 'sheet.dart';
2324
import 'theme.dart';
2425

2526
/// Modes that determine how to display the navigation bar's bottom in relation to scroll events.
@@ -212,7 +213,9 @@ bool _isTransitionable(BuildContext context) {
212213
// Fullscreen dialogs never transitions their nav bar with other push-style
213214
// pages' nav bars or with other fullscreen dialog pages on the way in or on
214215
// the way out.
215-
return route is PageRoute && !route.fullscreenDialog;
216+
return route is PageRoute &&
217+
!route.fullscreenDialog &&
218+
!CupertinoSheetRoute.hasParentSheet(context);
216219
}
217220

218221
/// An iOS-styled navigation bar.
@@ -1571,7 +1574,10 @@ class _PersistentNavigationBar extends StatelessWidget {
15711574
final Widget? backChevron = components.backChevron;
15721575
final Widget? backLabel = components.backLabel;
15731576

1574-
if (leading == null && backChevron != null && backLabel != null) {
1577+
if (leading == null &&
1578+
backChevron != null &&
1579+
backLabel != null &&
1580+
!CupertinoSheetRoute.hasParentSheet(context)) {
15751581
leading = CupertinoNavigationBarBackButton._assemble(backChevron, backLabel);
15761582
}
15771583

@@ -1591,7 +1597,11 @@ class _PersistentNavigationBar extends StatelessWidget {
15911597

15921598
return SizedBox(
15931599
height: _kNavBarPersistentHeight + MediaQuery.paddingOf(context).top,
1594-
child: SafeArea(bottom: false, child: paddedToolbar),
1600+
child: SafeArea(
1601+
top: !CupertinoSheetRoute.hasParentSheet(context),
1602+
bottom: false,
1603+
child: paddedToolbar,
1604+
),
15951605
);
15961606
}
15971607
}

packages/flutter/test/cupertino/nav_bar_test.dart

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,6 @@ void main() {
461461
testWidgets('Media padding is applied to CupertinoSliverNavigationBar', (
462462
WidgetTester tester,
463463
) async {
464-
final ScrollController scrollController = ScrollController();
465-
addTearDown(scrollController.dispose);
466464
final Key leadingKey = GlobalKey();
467465
final Key middleKey = GlobalKey();
468466
final Key trailingKey = GlobalKey();
@@ -475,7 +473,6 @@ void main() {
475473
),
476474
child: CupertinoPageScaffold(
477475
child: CustomScrollView(
478-
controller: scrollController,
479476
slivers: <Widget>[
480477
CupertinoSliverNavigationBar(
481478
leading: Placeholder(key: leadingKey),
@@ -797,6 +794,57 @@ void main() {
797794
expect(find.text('Home page'), findsOneWidget);
798795
});
799796

797+
testWidgets('Navigation bars in a CupertinoSheetRoute have no back button', (
798+
WidgetTester tester,
799+
) async {
800+
await tester.pumpWidget(
801+
const CupertinoApp(home: CupertinoNavigationBar(middle: Text('Home page'))),
802+
);
803+
804+
expect(find.byType(CupertinoButton), findsNothing);
805+
806+
tester
807+
.state<NavigatorState>(find.byType(Navigator))
808+
.push(
809+
CupertinoSheetRoute<void>(
810+
builder: (BuildContext context) {
811+
return const CupertinoPageScaffold(
812+
navigationBar: CupertinoNavigationBar(middle: Text('Page 2')),
813+
child: Placeholder(),
814+
);
815+
},
816+
),
817+
);
818+
819+
await tester.pump();
820+
await tester.pump(const Duration(milliseconds: 600));
821+
822+
// No back button is found.
823+
expect(find.byType(CupertinoButton), findsNothing);
824+
expect(find.text(String.fromCharCode(CupertinoIcons.back.codePoint)), findsNothing);
825+
826+
tester
827+
.state<NavigatorState>(find.byType(Navigator))
828+
.push(
829+
CupertinoSheetRoute<void>(
830+
builder: (BuildContext context) {
831+
return const CupertinoPageScaffold(
832+
child: CustomScrollView(
833+
slivers: <Widget>[CupertinoSliverNavigationBar(largeTitle: Text('Page 3'))],
834+
),
835+
);
836+
},
837+
),
838+
);
839+
840+
await tester.pump();
841+
await tester.pump(const Duration(milliseconds: 600));
842+
843+
// No back button is found.
844+
expect(find.byType(CupertinoButton), findsNothing);
845+
expect(find.text(String.fromCharCode(CupertinoIcons.back.codePoint)), findsNothing);
846+
});
847+
800848
testWidgets('Long back label turns into "back"', (WidgetTester tester) async {
801849
await tester.pumpWidget(const CupertinoApp(home: Placeholder()));
802850

packages/flutter/test/cupertino/nav_bar_transition_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,53 @@ void main() {
343343
expect(() => flying(tester, find.text('Page 2')), throwsAssertionError);
344344
});
345345

346+
testWidgets('Navigation bars in a CupertinoSheetRoute have no hero transitions', (
347+
WidgetTester tester,
348+
) async {
349+
await tester.pumpWidget(
350+
CupertinoApp(
351+
builder: (BuildContext context, Widget? navigator) {
352+
return navigator!;
353+
},
354+
home: const Placeholder(),
355+
),
356+
);
357+
358+
tester
359+
.state<NavigatorState>(find.byType(Navigator))
360+
.push(
361+
CupertinoSheetRoute<void>(
362+
builder:
363+
(BuildContext context) =>
364+
scaffoldForNavBar(const CupertinoNavigationBar(middle: Text('Page 1')))!,
365+
),
366+
);
367+
368+
await tester.pump();
369+
await tester.pump(const Duration(milliseconds: 600));
370+
371+
tester
372+
.state<NavigatorState>(find.byType(Navigator))
373+
.push(
374+
CupertinoSheetRoute<void>(
375+
builder:
376+
(BuildContext context) =>
377+
scaffoldForNavBar(
378+
const CupertinoSliverNavigationBar(largeTitle: Text('Page 2')),
379+
)!,
380+
),
381+
);
382+
383+
await tester.pump();
384+
await tester.pump(const Duration(milliseconds: 50));
385+
386+
expect(find.byType(Hero), findsNothing);
387+
388+
// No Hero transition happened.
389+
expect(() => flying(tester, find.text('Page 1')), throwsAssertionError);
390+
expect(() => flying(tester, find.text('Page 2')), throwsAssertionError);
391+
});
392+
346393
testWidgets('Popping mid-transition is symmetrical', (WidgetTester tester) async {
347394
await startTransitionBetween(tester, fromTitle: 'Page 1');
348395

0 commit comments

Comments
 (0)