[flutter_adaptive_scaffold] Call onDestinationSelected in bottom nav. (#2668)

This commit is contained in:
Alex Wallen
2022-10-03 09:40:17 -07:00
committed by GitHub
parent 41a99257c1
commit 78a66f81e3
4 changed files with 166 additions and 47 deletions

View File

@ -1,3 +1,7 @@
## 0.0.5
* Calls onDestinationChanged callback in bottom nav bar.
## 0.0.4
* Fix static analyzer warnings using `core` lint.

View File

@ -212,7 +212,7 @@ class AdaptiveScaffold extends StatefulWidget {
/// Option to override the drawerBreakpoint for the usage of [Drawer] over the
/// usual [BottomNavigationBar].
///
/// Defaults to [Breakpoints.onlySmallDesktop].
/// Defaults to [Breakpoints.smallDesktop].
final Breakpoint drawerBreakpoint;
/// Option to override the default [AppBar] when using drawer in desktop
@ -303,10 +303,12 @@ class AdaptiveScaffold extends StatefulWidget {
/// Public helper method to be used for creating a [BottomNavigationBar] from
/// a list of [NavigationDestination]s.
static Builder standardBottomNavigationBar(
{required List<NavigationDestination> destinations,
int currentIndex = 0,
double iconSize = 24}) {
static Builder standardBottomNavigationBar({
required List<NavigationDestination> destinations,
int currentIndex = 0,
double iconSize = 24,
ValueChanged<int>? onDestinationSelected,
}) {
return Builder(
builder: (_) {
return BottomNavigationBar(
@ -315,6 +317,7 @@ class AdaptiveScaffold extends StatefulWidget {
items: destinations
.map((NavigationDestination e) => _toBottomNavItem(e))
.toList(),
onTap: onDestinationSelected,
);
},
);
@ -524,7 +527,10 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
key: const Key('bottomNavigation'),
builder: (_) =>
AdaptiveScaffold.standardBottomNavigationBar(
destinations: widget.destinations),
currentIndex: widget.selectedIndex,
destinations: widget.destinations,
onDestinationSelected: widget.onSelectedIndexChange,
),
),
},
)

View File

@ -1,6 +1,6 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
version: 0.0.4
version: 0.0.5
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold

View File

@ -20,7 +20,8 @@ void main() {
final Finder primaryNav = find.byKey(const Key('primaryNavigation'));
final Finder primaryNav1 = find.byKey(const Key('primaryNavigation1'));
await tester.pumpWidget(await scaffold(width: 300, tester: tester));
await tester.binding.setSurfaceSize(SimulatedLayout.mobile.size);
await tester.pumpWidget(SimulatedLayout.mobile.app());
await tester.pumpAndSettle();
expect(smallBody, findsOneWidget);
@ -29,10 +30,11 @@ void main() {
expect(primaryNav, findsNothing);
expect(tester.getTopLeft(smallBody), Offset.zero);
expect(tester.getTopLeft(smallSBody), const Offset(150, 0));
expect(tester.getTopLeft(smallSBody), const Offset(200, 0));
expect(tester.getTopLeft(bottomNav), const Offset(0, 744));
await tester.pumpWidget(await scaffold(width: 900, tester: tester));
await tester.binding.setSurfaceSize(SimulatedLayout.tablet.size);
await tester.pumpWidget(SimulatedLayout.tablet.app());
await tester.pumpAndSettle();
expect(smallBody, findsNothing);
@ -43,11 +45,12 @@ void main() {
expect(primaryNav, findsOneWidget);
expect(tester.getTopLeft(body), const Offset(88, 0));
expect(tester.getTopLeft(sBody), const Offset(450, 0));
expect(tester.getTopLeft(sBody), const Offset(400, 0));
expect(tester.getTopLeft(primaryNav), Offset.zero);
expect(tester.getBottomRight(primaryNav), const Offset(88, 800));
await tester.pumpWidget(await scaffold(width: 1100, tester: tester));
await tester.binding.setSurfaceSize(SimulatedLayout.desktop.size);
await tester.pumpWidget(SimulatedLayout.desktop.app());
await tester.pumpAndSettle();
expect(body, findsNothing);
@ -68,8 +71,10 @@ void main() {
final Finder b = find.byKey(const Key('body'));
final Finder sBody = find.byKey(const Key('sBody'));
await tester.pumpWidget(await scaffold(width: 400, tester: tester));
await tester.pumpWidget(await scaffold(width: 800, tester: tester));
await tester.binding.setSurfaceSize(SimulatedLayout.mobile.size);
await tester.pumpWidget(SimulatedLayout.mobile.app());
await tester.binding.setSurfaceSize(SimulatedLayout.tablet.size);
await tester.pumpWidget(SimulatedLayout.tablet.app());
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
@ -107,10 +112,11 @@ void main() {
final Finder b = find.byKey(const Key('body'));
final Finder sBody = find.byKey(const Key('sBody'));
await tester.pumpWidget(
await scaffold(width: 400, tester: tester, animations: false));
await tester.pumpWidget(
await scaffold(width: 800, tester: tester, animations: false));
await tester.binding.setSurfaceSize(SimulatedLayout.mobile.size);
await tester.pumpWidget(SimulatedLayout.mobile.app(animations: false));
await tester.binding.setSurfaceSize(SimulatedLayout.tablet.size);
await tester.pumpWidget(SimulatedLayout.tablet.app(animations: false));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
@ -120,6 +126,48 @@ void main() {
expect(tester.getTopLeft(sBody), const Offset(400, 0));
expect(tester.getBottomRight(sBody), const Offset(800, 800));
});
// The goal of this test is to run through each of the navigation elements
// and test whether tapping on that element will update the selected index
// globally
testWidgets('tapping navigation elements calls onSelectedIndexChange',
(WidgetTester tester) async {
// for each screen size there is a different navigational element that
// we want to test tapping to set the selected index
await Future.forEach(SimulatedLayout.values,
(SimulatedLayout region) async {
int selectedIndex = 0;
final MaterialApp app = region.app(initialIndex: selectedIndex);
await tester.binding.setSurfaceSize(region.size);
await tester.pumpWidget(app);
await tester.pumpAndSettle();
// tap on the next icon
selectedIndex = (selectedIndex + 1) % TestScaffold.destinations.length;
// Resolve the icon that should be found
final NavigationDestination destination =
TestScaffold.destinations[selectedIndex];
expect(destination.icon, isA<Icon>());
final Icon icon = destination.icon as Icon;
expect(icon.icon, isNotNull);
// Find the icon in the application to tap
final Widget navigationSlot =
tester.widget(find.byKey(Key(region.navSlotKey)));
final Finder target =
find.widgetWithIcon(navigationSlot.runtimeType, icon.icon!);
expect(target, findsOneWidget);
await tester.tap(target);
await tester.pumpAndSettle();
// Check that the state was set appropriately
final Finder scaffold = find.byType(TestScaffold);
final TestScaffoldState state = tester.state<TestScaffoldState>(scaffold);
expect(selectedIndex, state.index);
});
});
}
class TestBreakpoint0 extends Breakpoint {
@ -152,34 +200,95 @@ class NeverOnBreakpoint extends Breakpoint {
}
}
Future<MaterialApp> scaffold({
required double width,
required WidgetTester tester,
bool animations = true,
}) async {
await tester.binding.setSurfaceSize(Size(width, 800));
return MaterialApp(
home: MediaQuery(
data: MediaQueryData(size: Size(width, 800)),
child: AdaptiveScaffold(
drawerBreakpoint: NeverOnBreakpoint(),
internalAnimations: animations,
smallBreakpoint: TestBreakpoint0(),
mediumBreakpoint: TestBreakpoint800(),
largeBreakpoint: TestBreakpoint1000(),
destinations: const <NavigationDestination>[
NavigationDestination(icon: Icon(Icons.inbox), label: 'Inbox'),
NavigationDestination(icon: Icon(Icons.article), label: 'Articles'),
NavigationDestination(icon: Icon(Icons.chat), label: 'Chat'),
NavigationDestination(icon: Icon(Icons.video_call), label: 'Video'),
],
smallBody: (_) => Container(color: Colors.red),
body: (_) => Container(color: Colors.green),
largeBody: (_) => Container(color: Colors.blue),
smallSecondaryBody: (_) => Container(color: Colors.red),
secondaryBody: (_) => Container(color: Colors.green),
largeSecondaryBody: (_) => Container(color: Colors.blue),
),
class TestScaffold extends StatefulWidget {
const TestScaffold({
super.key,
this.initialIndex = 0,
this.isAnimated = true,
});
final int initialIndex;
final bool isAnimated;
static const List<NavigationDestination> destinations =
<NavigationDestination>[
NavigationDestination(
key: Key('Inbox'),
icon: Icon(Icons.inbox),
label: 'Inbox',
),
);
NavigationDestination(
key: Key('Articles'),
icon: Icon(Icons.article),
label: 'Articles',
),
NavigationDestination(
key: Key('Chat'),
icon: Icon(Icons.chat),
label: 'Chat',
),
];
@override
State<TestScaffold> createState() => TestScaffoldState();
}
class TestScaffoldState extends State<TestScaffold> {
late int index = widget.initialIndex;
@override
Widget build(BuildContext context) {
return AdaptiveScaffold(
selectedIndex: index,
onSelectedIndexChange: (int index) {
setState(() {
this.index = index;
});
},
drawerBreakpoint: NeverOnBreakpoint(),
internalAnimations: widget.isAnimated,
smallBreakpoint: TestBreakpoint0(),
mediumBreakpoint: TestBreakpoint800(),
largeBreakpoint: TestBreakpoint1000(),
destinations: TestScaffold.destinations,
smallBody: (_) => Container(color: Colors.red),
body: (_) => Container(color: Colors.green),
largeBody: (_) => Container(color: Colors.blue),
smallSecondaryBody: (_) => Container(color: Colors.red),
secondaryBody: (_) => Container(color: Colors.green),
largeSecondaryBody: (_) => Container(color: Colors.blue),
);
}
}
enum SimulatedLayout {
mobile(width: 400, navSlotKey: 'bottomNavigation'),
tablet(width: 800, navSlotKey: 'primaryNavigation'),
desktop(width: 1100, navSlotKey: 'primaryNavigation1');
const SimulatedLayout({
required double width,
required this.navSlotKey,
}) : _width = width;
final double _width;
final double _height = 800;
final String navSlotKey;
Size get size => Size(_width, _height);
MaterialApp app({
int initialIndex = 0,
bool animations = true,
}) {
return MaterialApp(
home: MediaQuery(
data: MediaQueryData(size: size),
child: TestScaffold(
initialIndex: initialIndex,
isAnimated: animations,
),
),
);
}
}