mirror of
https://github.com/flutter/packages.git
synced 2025-07-03 09:08:54 +08:00
[flutter_adaptive_scaffold] Call onDestinationSelected in bottom nav. (#2668)
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
* Calls onDestinationChanged callback in bottom nav bar.
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
* Fix static analyzer warnings using `core` lint.
|
* Fix static analyzer warnings using `core` lint.
|
||||||
|
@ -212,7 +212,7 @@ class AdaptiveScaffold extends StatefulWidget {
|
|||||||
/// Option to override the drawerBreakpoint for the usage of [Drawer] over the
|
/// Option to override the drawerBreakpoint for the usage of [Drawer] over the
|
||||||
/// usual [BottomNavigationBar].
|
/// usual [BottomNavigationBar].
|
||||||
///
|
///
|
||||||
/// Defaults to [Breakpoints.onlySmallDesktop].
|
/// Defaults to [Breakpoints.smallDesktop].
|
||||||
final Breakpoint drawerBreakpoint;
|
final Breakpoint drawerBreakpoint;
|
||||||
|
|
||||||
/// Option to override the default [AppBar] when using drawer in desktop
|
/// 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
|
/// Public helper method to be used for creating a [BottomNavigationBar] from
|
||||||
/// a list of [NavigationDestination]s.
|
/// a list of [NavigationDestination]s.
|
||||||
static Builder standardBottomNavigationBar(
|
static Builder standardBottomNavigationBar({
|
||||||
{required List<NavigationDestination> destinations,
|
required List<NavigationDestination> destinations,
|
||||||
int currentIndex = 0,
|
int currentIndex = 0,
|
||||||
double iconSize = 24}) {
|
double iconSize = 24,
|
||||||
|
ValueChanged<int>? onDestinationSelected,
|
||||||
|
}) {
|
||||||
return Builder(
|
return Builder(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return BottomNavigationBar(
|
return BottomNavigationBar(
|
||||||
@ -315,6 +317,7 @@ class AdaptiveScaffold extends StatefulWidget {
|
|||||||
items: destinations
|
items: destinations
|
||||||
.map((NavigationDestination e) => _toBottomNavItem(e))
|
.map((NavigationDestination e) => _toBottomNavItem(e))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
onTap: onDestinationSelected,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -524,7 +527,10 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
|
|||||||
key: const Key('bottomNavigation'),
|
key: const Key('bottomNavigation'),
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
AdaptiveScaffold.standardBottomNavigationBar(
|
AdaptiveScaffold.standardBottomNavigationBar(
|
||||||
destinations: widget.destinations),
|
currentIndex: widget.selectedIndex,
|
||||||
|
destinations: widget.destinations,
|
||||||
|
onDestinationSelected: widget.onSelectedIndexChange,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: flutter_adaptive_scaffold
|
name: flutter_adaptive_scaffold
|
||||||
description: Widgets to easily build adaptive layouts, including navigation elements.
|
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
|
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
|
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ void main() {
|
|||||||
final Finder primaryNav = find.byKey(const Key('primaryNavigation'));
|
final Finder primaryNav = find.byKey(const Key('primaryNavigation'));
|
||||||
final Finder primaryNav1 = find.byKey(const Key('primaryNavigation1'));
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(smallBody, findsOneWidget);
|
expect(smallBody, findsOneWidget);
|
||||||
@ -29,10 +30,11 @@ void main() {
|
|||||||
expect(primaryNav, findsNothing);
|
expect(primaryNav, findsNothing);
|
||||||
|
|
||||||
expect(tester.getTopLeft(smallBody), Offset.zero);
|
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));
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(smallBody, findsNothing);
|
expect(smallBody, findsNothing);
|
||||||
@ -43,11 +45,12 @@ void main() {
|
|||||||
expect(primaryNav, findsOneWidget);
|
expect(primaryNav, findsOneWidget);
|
||||||
|
|
||||||
expect(tester.getTopLeft(body), const Offset(88, 0));
|
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.getTopLeft(primaryNav), Offset.zero);
|
||||||
expect(tester.getBottomRight(primaryNav), const Offset(88, 800));
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(body, findsNothing);
|
expect(body, findsNothing);
|
||||||
@ -68,8 +71,10 @@ void main() {
|
|||||||
final Finder b = find.byKey(const Key('body'));
|
final Finder b = find.byKey(const Key('body'));
|
||||||
final Finder sBody = find.byKey(const Key('sBody'));
|
final Finder sBody = find.byKey(const Key('sBody'));
|
||||||
|
|
||||||
await tester.pumpWidget(await scaffold(width: 400, tester: tester));
|
await tester.binding.setSurfaceSize(SimulatedLayout.mobile.size);
|
||||||
await tester.pumpWidget(await scaffold(width: 800, tester: tester));
|
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();
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
@ -107,10 +112,11 @@ void main() {
|
|||||||
final Finder b = find.byKey(const Key('body'));
|
final Finder b = find.byKey(const Key('body'));
|
||||||
final Finder sBody = find.byKey(const Key('sBody'));
|
final Finder sBody = find.byKey(const Key('sBody'));
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.binding.setSurfaceSize(SimulatedLayout.mobile.size);
|
||||||
await scaffold(width: 400, tester: tester, animations: false));
|
await tester.pumpWidget(SimulatedLayout.mobile.app(animations: false));
|
||||||
await tester.pumpWidget(
|
|
||||||
await scaffold(width: 800, tester: tester, animations: false));
|
await tester.binding.setSurfaceSize(SimulatedLayout.tablet.size);
|
||||||
|
await tester.pumpWidget(SimulatedLayout.tablet.app(animations: false));
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
@ -120,6 +126,48 @@ void main() {
|
|||||||
expect(tester.getTopLeft(sBody), const Offset(400, 0));
|
expect(tester.getTopLeft(sBody), const Offset(400, 0));
|
||||||
expect(tester.getBottomRight(sBody), const Offset(800, 800));
|
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 {
|
class TestBreakpoint0 extends Breakpoint {
|
||||||
@ -152,34 +200,95 @@ class NeverOnBreakpoint extends Breakpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MaterialApp> scaffold({
|
class TestScaffold extends StatefulWidget {
|
||||||
required double width,
|
const TestScaffold({
|
||||||
required WidgetTester tester,
|
super.key,
|
||||||
bool animations = true,
|
this.initialIndex = 0,
|
||||||
}) async {
|
this.isAnimated = true,
|
||||||
await tester.binding.setSurfaceSize(Size(width, 800));
|
});
|
||||||
return MaterialApp(
|
|
||||||
home: MediaQuery(
|
final int initialIndex;
|
||||||
data: MediaQueryData(size: Size(width, 800)),
|
final bool isAnimated;
|
||||||
child: AdaptiveScaffold(
|
|
||||||
drawerBreakpoint: NeverOnBreakpoint(),
|
static const List<NavigationDestination> destinations =
|
||||||
internalAnimations: animations,
|
<NavigationDestination>[
|
||||||
smallBreakpoint: TestBreakpoint0(),
|
NavigationDestination(
|
||||||
mediumBreakpoint: TestBreakpoint800(),
|
key: Key('Inbox'),
|
||||||
largeBreakpoint: TestBreakpoint1000(),
|
icon: Icon(Icons.inbox),
|
||||||
destinations: const <NavigationDestination>[
|
label: 'Inbox',
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user