Line data Source code
1 : import 'package:flutter/material.dart';
2 :
3 : import '../destination.dart';
4 : import '../navigation_controller.dart';
5 : import 'index.dart';
6 :
7 : /// A [NavigatorBuilder] that allows to switch between destinations using
8 : /// [BottomNavigationBar].
9 : ///
10 : /// It builds a wrapper widget, which is a [Scaffold] with a [Scaffold.body] set
11 : /// to the current destination's content, and [Scaffold.bottomNavigationBar]
12 : /// specified.
13 : ///
14 : /// The [bottomNavigationItems] must correspond to the navigator's destinations.
15 : ///
16 : /// The bottom navigation bar can be customized using [parameters], which includes
17 : /// all parameters supported by the [BottomNavigationBar] widget.
18 : ///
19 : /// See also:
20 : /// - [NavigatorBuilder]
21 : /// - [BottomNavigationBarParameters]
22 : /// - [NavigationController]
23 : /// - [BottomNavigationBar]
24 : ///
25 : class BottomNavigationBuilder extends NavigatorBuilder {
26 : /// Creates a [BottomNavigationBuilder] instance.
27 : ///
28 0 : const BottomNavigationBuilder({
29 : this.bottomNavigationItems = const <BottomNavigationBarItem>[],
30 : this.parameters = const BottomNavigationBarParameters(),
31 : this.navigationBarItems = const <NavigationDestination>[],
32 : this.navigationBarParameters = const NavigationBarParameters(),
33 : bool? material3,
34 : }) : _material3 = material3 ?? false,
35 0 : super();
36 :
37 : /// Creates a [BottomNavigationBuilder] instance that uses Material 3 [NavigationBar]
38 : /// widget.
39 : ///
40 0 : factory BottomNavigationBuilder.navigationBar({
41 : required List<NavigationDestination> navigationBarItems,
42 : NavigationBarParameters? navigationBarParameters,
43 : }) =>
44 0 : BottomNavigationBuilder(
45 : navigationBarItems: navigationBarItems,
46 : navigationBarParameters:
47 : navigationBarParameters ?? const NavigationBarParameters(),
48 : material3: true,
49 : );
50 :
51 : /// A list of [BottomNavigationBarItems], that corresponds to the navigator's
52 : /// destination list.
53 : ///
54 : /// The list must contain the same number of bottom navigation bar items,
55 : /// following with the same order as a destination list specified for the navigator.
56 : ///
57 : final List<BottomNavigationBarItem> bottomNavigationItems;
58 :
59 : /// A set of [BottomNavigationBar] parameters.
60 : ///
61 : /// Contains all supported parameters to customize [BottomNavigationBar] widget.
62 : /// Doesn't include 'items', 'onTap' and 'currentIndex', which are managed by
63 : /// [BottomNavigationBuilder].
64 : ///
65 : final BottomNavigationBarParameters parameters;
66 :
67 : /// A list of [NavigationDestination] widgets, that corresponds to the navigator's
68 : /// destination list.
69 : ///
70 : /// The list must contain the same number of items, following with the same order
71 : /// as a destination list specified in the navigator.
72 : ///
73 : final List<NavigationDestination> navigationBarItems;
74 :
75 : /// A set of [NavigationBar] parameters.
76 : ///
77 : /// Contains all supported parameters to customize [NavigationBar] widget.
78 : /// Doesn't include 'items', 'onTap' and 'currentIndex', which are managed by
79 : /// [BottomNavigationBuilder].
80 : ///
81 : final NavigationBarParameters navigationBarParameters;
82 :
83 : final bool _material3;
84 :
85 0 : @override
86 : Widget build(BuildContext context, NavigationController navigator) {
87 0 : final currentDestination = navigator.currentDestination;
88 0 : return _BottomNavigationWrapper(
89 : destination: currentDestination,
90 0 : onSelectBottomTab: (index) =>
91 0 : navigator.goTo(navigator.destinations[index]),
92 0 : selectedIndex: navigator.destinations.indexOf(currentDestination),
93 0 : items: bottomNavigationItems,
94 0 : parameters: parameters,
95 0 : navigationBarItems: navigationBarItems,
96 0 : navigationBarParameters: navigationBarParameters,
97 0 : material3: _material3,
98 : );
99 : }
100 : }
101 :
102 : class _BottomNavigationWrapper extends StatefulWidget {
103 0 : const _BottomNavigationWrapper({
104 : Key? key,
105 : required this.destination,
106 : required this.onSelectBottomTab,
107 : required this.selectedIndex,
108 : this.items = const <BottomNavigationBarItem>[],
109 : this.parameters = const BottomNavigationBarParameters(),
110 : this.navigationBarItems = const <NavigationDestination>[],
111 : this.navigationBarParameters = const NavigationBarParameters(),
112 : this.material3 = false,
113 0 : }) : super(key: key);
114 :
115 : final Destination destination;
116 :
117 : final List<BottomNavigationBarItem> items;
118 :
119 : final void Function(int) onSelectBottomTab;
120 :
121 : final int selectedIndex;
122 :
123 : final BottomNavigationBarParameters parameters;
124 :
125 : final List<NavigationDestination> navigationBarItems;
126 :
127 : final NavigationBarParameters navigationBarParameters;
128 :
129 : final bool material3;
130 :
131 0 : @override
132 : _BottomNavigationWrapperState createState() =>
133 0 : _BottomNavigationWrapperState();
134 : }
135 :
136 : class _BottomNavigationWrapperState extends State<_BottomNavigationWrapper> {
137 : final _content = <Destination, Widget>{};
138 :
139 : final _indexes = <Destination, int>{};
140 :
141 : late final OverlayEntry _mainOverlay;
142 :
143 0 : @override
144 : void initState() {
145 0 : super.initState();
146 0 : _content[widget.destination] = widget.destination.build(context);
147 0 : _indexes[widget.destination] = widget.selectedIndex;
148 0 : _mainOverlay = OverlayEntry(
149 0 : builder: (context) => Scaffold(
150 0 : body: Stack(
151 0 : children: [
152 0 : ..._content.entries
153 0 : .map((entry) => Offstage(
154 0 : offstage: _indexes[entry.key] != widget.selectedIndex,
155 0 : child: entry.value,
156 : ))
157 0 : .toList(),
158 : ],
159 : ),
160 0 : bottomNavigationBar: widget.material3
161 0 : ? NavigationBar(
162 : animationDuration:
163 0 : widget.navigationBarParameters.animationDuration,
164 0 : selectedIndex: widget.selectedIndex,
165 0 : destinations: widget.navigationBarItems,
166 0 : onDestinationSelected: widget.onSelectBottomTab,
167 0 : backgroundColor: widget.navigationBarParameters.backgroundColor,
168 0 : elevation: widget.navigationBarParameters.elevation,
169 0 : height: widget.navigationBarParameters.height,
170 0 : labelBehavior: widget.navigationBarParameters.labelBehavior,
171 : )
172 0 : : BottomNavigationBar(
173 0 : items: widget.items,
174 0 : currentIndex: widget.selectedIndex,
175 0 : onTap: widget.onSelectBottomTab,
176 0 : elevation: widget.parameters.elevation,
177 0 : type: widget.parameters.type,
178 0 : fixedColor: widget.parameters.fixedColor,
179 0 : backgroundColor: widget.parameters.backgroundColor,
180 0 : iconSize: widget.parameters.iconSize,
181 0 : selectedItemColor: widget.parameters.selectedItemColor,
182 0 : unselectedItemColor: widget.parameters.unselectedItemColor,
183 0 : selectedIconTheme: widget.parameters.selectedIconTheme,
184 0 : unselectedIconTheme: widget.parameters.unselectedIconTheme,
185 0 : selectedFontSize: widget.parameters.selectedFontSize,
186 0 : unselectedFontSize: widget.parameters.unselectedFontSize,
187 0 : selectedLabelStyle: widget.parameters.selectedLabelStyle,
188 0 : unselectedLabelStyle: widget.parameters.unselectedLabelStyle,
189 0 : showSelectedLabels: widget.parameters.showSelectedLabels,
190 0 : showUnselectedLabels: widget.parameters.showUnselectedLabels,
191 0 : mouseCursor: widget.parameters.mouseCursor,
192 0 : enableFeedback: widget.parameters.enableFeedback,
193 0 : landscapeLayout: widget.parameters.landscapeLayout,
194 : ),
195 : ),
196 : );
197 : }
198 :
199 0 : @override
200 : void didUpdateWidget(_BottomNavigationWrapper oldWidget) {
201 0 : super.didUpdateWidget(oldWidget);
202 : bool needsRebuild = false;
203 0 : if (widget.material3 != oldWidget.material3) {
204 : needsRebuild = true;
205 : }
206 0 : if (!widget.destination.isFinalDestination) {
207 : needsRebuild = true;
208 0 : _content[widget.destination] = widget.destination.build(context);
209 0 : _indexes[widget.destination] = widget.selectedIndex;
210 0 : } else if (oldWidget.selectedIndex != widget.selectedIndex &&
211 0 : !_content.containsKey(widget.destination)) {
212 : needsRebuild = true;
213 0 : _content[widget.destination] = widget.destination.build(context);
214 0 : _indexes[widget.destination] = widget.selectedIndex;
215 : }
216 : if (needsRebuild) {
217 0 : _mainOverlay.markNeedsBuild();
218 : }
219 : }
220 :
221 0 : @override
222 : Widget build(BuildContext context) {
223 0 : return Overlay(
224 0 : initialEntries: [
225 0 : _mainOverlay,
226 : ],
227 : );
228 : }
229 : }
230 :
231 : /// Contains parameters to customize the [BottomNavigationBar].
232 : ///
233 : /// It includes all the same arguments as the [BottomNavigationBar()], excepting
234 : /// the 'items', 'onTap' and 'currentIndex', which are managed by the [BottomNavigationBuilder].
235 : ///
236 : /// See also:
237 : /// - [BottomNavigationBuilder]
238 : /// - [BottomNavigationBar]
239 : ///
240 : class BottomNavigationBarParameters {
241 : /// Create a [BottomNavigationBarParameters] instance.
242 : ///
243 11 : const BottomNavigationBarParameters({
244 : this.elevation,
245 : this.type,
246 : this.fixedColor,
247 : this.backgroundColor,
248 : this.iconSize = 24.0,
249 : this.selectedItemColor,
250 : this.unselectedItemColor,
251 : this.selectedIconTheme,
252 : this.unselectedIconTheme,
253 : this.selectedFontSize = 14.0,
254 : this.unselectedFontSize = 12.0,
255 : this.selectedLabelStyle,
256 : this.unselectedLabelStyle,
257 : this.showSelectedLabels,
258 : this.showUnselectedLabels,
259 : this.mouseCursor,
260 : this.enableFeedback,
261 : this.landscapeLayout,
262 : });
263 :
264 : /// [BottomNavigationBar.elevation]
265 : ///
266 : final double? elevation;
267 :
268 : /// [BottomNavigationBar.type]
269 : ///
270 : final BottomNavigationBarType? type;
271 :
272 : /// [BottomNavigationBar.fixedColor]
273 : ///
274 : final Color? fixedColor;
275 :
276 : /// [BottomNavigationBar.backgroundColor]
277 : ///
278 : final Color? backgroundColor;
279 :
280 : /// [BottomNavigationBar.iconSize]
281 : ///
282 : final double iconSize;
283 :
284 : /// [BottomNavigationBar.selectedItemColor]
285 : ///
286 : final Color? selectedItemColor;
287 :
288 : /// [BottomNavigationBar.unselectedItemColor]
289 : ///
290 : final Color? unselectedItemColor;
291 :
292 : /// [BottomNavigationBar.selectedIconTheme]
293 : ///
294 : final IconThemeData? selectedIconTheme;
295 :
296 : /// [BottomNavigationBar.unselectedIconTheme]
297 : ///
298 : final IconThemeData? unselectedIconTheme;
299 :
300 : /// [BottomNavigationBar.selectedLabelStyle]
301 : ///
302 : final TextStyle? selectedLabelStyle;
303 :
304 : /// [BottomNavigationBar.unselectedLabelStyle]
305 : ///
306 : final TextStyle? unselectedLabelStyle;
307 :
308 : /// [BottomNavigationBar.selectedFontSize]
309 : ///
310 : final double selectedFontSize;
311 :
312 : /// [BottomNavigationBar.unselectedFontSize]
313 : ///
314 : final double unselectedFontSize;
315 :
316 : /// [BottomNavigationBar.showUnselectedLabels]
317 : ///
318 : final bool? showUnselectedLabels;
319 :
320 : /// [BottomNavigationBar.showSelectedLabels]
321 : ///
322 : final bool? showSelectedLabels;
323 :
324 : /// [BottomNavigationBar.mouseCursor]
325 : ///
326 : final MouseCursor? mouseCursor;
327 :
328 : /// [BottomNavigationBar.enableFeedback]
329 : ///
330 : final bool? enableFeedback;
331 :
332 : /// [BottomNavigationBar.landscapeLayout]
333 : ///
334 : final BottomNavigationBarLandscapeLayout? landscapeLayout;
335 : }
336 :
337 : /// Contains parameters to customize the [NavigationBar].
338 : ///
339 : /// It includes all the same arguments as the [NavigationBar()], excepting
340 : /// the 'items', 'onTap' and 'currentIndex', which are managed by the [BottomNavigationBuilder].
341 : ///
342 : /// See also:
343 : /// - [BottomNavigationBuilder]
344 : /// - [NavigationBar]
345 : ///
346 : class NavigationBarParameters {
347 : /// Create a [NavigationBarParameters] instance.
348 : ///
349 11 : const NavigationBarParameters({
350 : this.animationDuration,
351 : this.backgroundColor,
352 : this.elevation,
353 : this.height,
354 : this.labelBehavior,
355 : });
356 :
357 : /// [NavigationBar.animationDuration]
358 : ///
359 : final Duration? animationDuration;
360 :
361 : /// [NavigationBar.backgroundColor]
362 : ///
363 : final Color? backgroundColor;
364 :
365 : /// [NavigationBar.elevation]
366 : ///
367 : final double? elevation;
368 :
369 : /// [NavigationBar.height]
370 : ///
371 : final double? height;
372 :
373 : /// [NavigationBar.labelBehavior]
374 : ///
375 : final NavigationDestinationLabelBehavior? labelBehavior;
376 : }
|