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