forked from firka/flutter
Add bottom to CupertinoSliverNavigationBar (#155841)
Fixes [Support search box inside large title nav bar](https://github.com/flutter/flutter/issues/18103) Part of [Support segmented controls in nav bars and double row nav bars](https://github.com/flutter/flutter/issues/10469) ## None mode (Current default) https://github.com/user-attachments/assets/d798314e-940f-4311-9a9a-fe999c65f280 ## Always mode https://github.com/user-attachments/assets/950a85aa-8ca2-42ea-bf8b-3cb8f95e616e ## Automatic mode https://github.com/user-attachments/assets/c7c7240b-d493-4036-a987-30f61d02bac3 ## With CupertinoSlidingSegmentedControl https://github.com/user-attachments/assets/59f4aec4-8d9c-4223-915b-97b73cb25dc8 <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 [CupertinoSliverNavigationBar]. void main() => runApp(const SliverNavBarApp()); class SliverNavBarApp extends StatelessWidget { const SliverNavBarApp({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( theme: CupertinoThemeData(brightness: Brightness.light), home: SliverNavBarExample(), ); } } class SliverNavBarExample extends StatelessWidget { const SliverNavBarExample({super.key}); @override Widget build(BuildContext context) { return const CupertinoPageScaffold( // A ScrollView that creates custom scroll effects using slivers. child: SafeArea( child: CustomScrollView( // A list of sliver widgets. slivers: <Widget>[ CupertinoSliverNavigationBar( leading: SizedBox( width: 100, child: Row( children: [ Icon(CupertinoIcons.back), Text( 'Lists', style: TextStyle(color: CupertinoColors.activeBlue), ), ], ), ), trailing: Icon(CupertinoIcons.plus), largeTitle: Text('iPhone'), // Change to desired mode. drawerMode: NavigationDrawerMode.none, drawer: Padding( padding: EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 16.0), child: CupertinoSearchTextField(), ), ), SliverFillRemaining( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text('Drag me up', textAlign: TextAlign.center), ], ), ), ], ), ), ); } } ``` </details>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
// 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 [CupertinoSliverNavigationBar].
|
||||
|
||||
void main() => runApp(const SliverNavBarApp());
|
||||
|
||||
class SliverNavBarApp extends StatelessWidget {
|
||||
const SliverNavBarApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const CupertinoApp(
|
||||
theme: CupertinoThemeData(brightness: Brightness.light),
|
||||
home: SliverNavBarExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute<Widget>(
|
||||
builder: (BuildContext context) {
|
||||
return const NextPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Bottom Automatic mode'),
|
||||
),
|
||||
CupertinoButton.filled(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute<Widget>(
|
||||
builder: (BuildContext context) {
|
||||
return const NextPage(
|
||||
bottomMode: NavigationBarBottomMode.always,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Bottom Always mode'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NextPage extends StatelessWidget {
|
||||
const NextPage({super.key, this.bottomMode = NavigationBarBottomMode.automatic});
|
||||
|
||||
final NavigationBarBottomMode bottomMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Brightness brightness = CupertinoTheme.brightnessOf(context);
|
||||
return CupertinoPageScaffold(
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverNavigationBar.search(
|
||||
backgroundColor: CupertinoColors.systemYellow,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: brightness == Brightness.light
|
||||
? CupertinoColors.black
|
||||
: CupertinoColors.white,
|
||||
),
|
||||
),
|
||||
middle: const Text('Contacts Group'),
|
||||
largeTitle: const Text('Family'),
|
||||
bottomMode: bottomMode,
|
||||
),
|
||||
const SliverFillRemaining(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Text('Drag me up', textAlign: TextAlign.center),
|
||||
Text('Tap on the leading button to navigate back',
|
||||
textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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';
|
||||
import 'package:flutter_api_samples/cupertino/nav_bar/cupertino_sliver_nav_bar.1.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
const Offset titleDragUp = Offset(0.0, -100.0);
|
||||
const Offset bottomDragUp = Offset(0.0, -50.0);
|
||||
|
||||
void main() {
|
||||
testWidgets('Collapse and expand CupertinoSliverNavigationBar changes title position', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.SliverNavBarApp(),
|
||||
);
|
||||
|
||||
// Large title is visible and at lower position.
|
||||
expect(tester.getBottomLeft(find.text('Contacts').first).dy, 88.0);
|
||||
await tester.fling(find.text('Drag me up'), titleDragUp, 500.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Large title is hidden and at higher position.
|
||||
expect(tester.getBottomLeft(find.text('Contacts').first).dy, 36.0 + 8.0); // Static part + _kNavBarBottomPadding.
|
||||
});
|
||||
|
||||
testWidgets('Search field is hidden in bottom automatic mode', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.SliverNavBarApp(),
|
||||
);
|
||||
|
||||
// Navigate to a page with bottom automatic mode.
|
||||
final Finder nextButton = find.text('Bottom Automatic mode');
|
||||
expect(nextButton, findsOneWidget);
|
||||
await tester.tap(nextButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Middle, large title, and search field are visible.
|
||||
expect(tester.getBottomLeft(find.text('Contacts Group').first).dy, 30.5);
|
||||
expect(tester.getBottomLeft(find.text('Family').first).dy, 88.0);
|
||||
expect(tester.getTopLeft(find.byType(CupertinoSearchTextField)).dy, 104.0);
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoSearchTextField)).dy, 139.0);
|
||||
|
||||
await tester.fling(find.text('Drag me up'), bottomDragUp, 50.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Search field is hidden, but large title and middle title are visible.
|
||||
expect(tester.getBottomLeft(find.text('Contacts Group').first).dy, 30.5);
|
||||
expect(tester.getBottomLeft(find.text('Family').first).dy, 88.0);
|
||||
expect(tester.getTopLeft(find.byType(CupertinoSearchTextField)).dy, 104.0);
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoSearchTextField)).dy, 104.0);
|
||||
|
||||
await tester.fling(find.text('Drag me up'), titleDragUp, 50.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Large title and search field are hidden and middle title is visible.
|
||||
expect(tester.getBottomLeft(find.text('Contacts Group').first).dy, 30.5);
|
||||
expect(tester.getBottomLeft(find.text('Family').first).dy, 36.0 + 8.0); // Static part + _kNavBarBottomPadding.
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoSearchTextField)).dy, 52.0);
|
||||
});
|
||||
|
||||
testWidgets('Search field is always shown in bottom always mode', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.SliverNavBarApp(),
|
||||
);
|
||||
|
||||
// Navigate to a page with bottom always mode.
|
||||
final Finder nextButton = find.text('Bottom Always mode');
|
||||
expect(nextButton, findsOneWidget);
|
||||
await tester.tap(nextButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Middle, large title, and search field are visible.
|
||||
expect(tester.getBottomLeft(find.text('Contacts Group').first).dy, 30.5);
|
||||
expect(tester.getBottomLeft(find.text('Family').first).dy, 88.0);
|
||||
expect(tester.getTopLeft(find.byType(CupertinoSearchTextField)).dy, 104.0);
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoSearchTextField)).dy, 139.0);
|
||||
|
||||
await tester.fling(find.text('Drag me up'), titleDragUp, 50.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Large title is hidden, but search field and middle title are visible.
|
||||
expect(tester.getBottomLeft(find.text('Contacts Group').first).dy, 30.5);
|
||||
expect(tester.getBottomLeft(find.text('Family').first).dy, 36.0 + 8.0); // Static part + _kNavBarBottomPadding.
|
||||
expect(tester.getTopLeft(find.byType(CupertinoSearchTextField)).dy, 52.0);
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoSearchTextField)).dy, 87.0);
|
||||
});
|
||||
|
||||
testWidgets('CupertinoSliverNavigationBar with previous route has back button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.SliverNavBarApp(),
|
||||
);
|
||||
|
||||
// Navigate to the first page.
|
||||
final Finder nextButton1 = find.text('Bottom Automatic mode');
|
||||
expect(nextButton1, findsOneWidget);
|
||||
await tester.tap(nextButton1);
|
||||
await tester.pumpAndSettle();
|
||||
expect(nextButton1, findsNothing);
|
||||
|
||||
// Go back to the previous page.
|
||||
final Finder backButton1 = find.byType(CupertinoButton);
|
||||
expect(backButton1, findsOneWidget);
|
||||
await tester.tap(backButton1);
|
||||
await tester.pumpAndSettle();
|
||||
expect(nextButton1, findsOneWidget);
|
||||
|
||||
// Navigate to the second page.
|
||||
final Finder nextButton2 = find.text('Bottom Always mode');
|
||||
expect(nextButton2, findsOneWidget);
|
||||
await tester.tap(nextButton2);
|
||||
await tester.pumpAndSettle();
|
||||
expect(nextButton2, findsNothing);
|
||||
|
||||
// Go back to the previous page.
|
||||
final Finder backButton2 = find.byType(CupertinoButton);
|
||||
expect(backButton2, findsOneWidget);
|
||||
await tester.tap(backButton2);
|
||||
await tester.pumpAndSettle();
|
||||
expect(nextButton2, findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user