Line data Source code
1 : import 'package:flutter/gestures.dart';
2 : import 'package:flutter/material.dart';
3 : import 'package:flutter/services.dart';
4 :
5 : import '../destination.dart';
6 : import '../navigation_controller.dart';
7 : import 'index.dart';
8 :
9 : /// A [NavigatorBuilder] that allows to switch between destinations using
10 : /// [TabBar].
11 : ///
12 : /// It builds a wrapper widget, which is a [Scaffold] with a [Scaffold.appbar] set
13 : /// to the [TabBar] with provided [tabs], and a [Scaffold.body] set to the [TabBarView],
14 : /// which display a content of corresponding destination.
15 : ///
16 : /// The [tabs] must correspond to the navigator's destinations.
17 : ///
18 : /// The tab bar can be customized using [parameters], which includes all parameters
19 : /// supported by the [TabBar] widget.
20 : ///
21 : /// See also:
22 : /// - [NavigatorBuilder]
23 : /// - [AppBarParameters]
24 : /// - [TabBarParameters]
25 : /// - [NavigationController]
26 : /// - [TabBar]
27 : ///
28 : class TabsNavigationBuilder implements NavigatorBuilder {
29 : /// Creates a [TabsNavigationBuilder] instance.
30 : ///
31 0 : const TabsNavigationBuilder({
32 : required this.tabs,
33 : this.parameters = const TabBarParameters(),
34 : this.appBarParametersBuilder,
35 : this.wrapInScaffold = true,
36 : });
37 :
38 : /// Typically a list of [Tab] widgets, that corresponds to the navigator's
39 : /// destination list.
40 : ///
41 : /// The list must contain the same number of widgets, following with the same order
42 : /// as a destination list specified for the navigator.
43 : ///
44 : final List<Widget> tabs;
45 :
46 : /// A set of [TabBar] parameters.
47 : ///
48 : /// Contains all supported parameters to customize [TabBar] widget.
49 : /// Doesn't include 'tabs', 'onTap' and 'controller', which are managed by
50 : /// [TabsNavigationBuilder].
51 : ///
52 : final TabBarParameters parameters;
53 :
54 : /// Return an instance of [AppBarParameters] for provided destination.
55 : ///
56 : /// Once this builder is specified, the navigation [TabBar] will appear as part of
57 : /// [AppBar] widget.
58 : /// When this function is called, the [destination] parameter is set to current
59 : /// destination (selected tab).
60 : ///
61 : /// The function should return an instance of [AppBarParameters],
62 : /// which is a set of all parameters available in the [AppBar] widget.
63 : /// So the app bar widget con be made to match the current destination.
64 : /// For example, you can set a title and actions, depending on the current destination.
65 : ///
66 : final AppBarParameters Function(BuildContext context, Destination destination)?
67 : appBarParametersBuilder;
68 :
69 : /// Controls if the [Scaffold] widget should be used around the tab bar and tab's content.
70 : ///
71 : /// This might be needed if you are using tabs as a top level navigation in your app.
72 : /// Defaults to 'true'.
73 : ///
74 : final bool wrapInScaffold;
75 :
76 0 : @override
77 : Widget build(BuildContext context, NavigationController navigator) {
78 0 : final currentDestination = navigator.currentDestination;
79 0 : return _TabsNavigationWrapper(
80 0 : tabs: tabs,
81 : // TODO: This implementation doesn't respect the possible parameters of destinations (excluding current destination).
82 : // How this could be resolved?
83 0 : tabContentBuilder: (tabIndex) =>
84 0 : navigator.destinations[tabIndex].build(context),
85 0 : parameters: parameters,
86 0 : onTabSelected: (index) => navigator.goTo(navigator.destinations[index]),
87 0 : selectedIndex: navigator.destinations.indexOf(currentDestination),
88 0 : appBarParameters: appBarParametersBuilder?.call(context, currentDestination),
89 0 : wrapInScaffold: wrapInScaffold,
90 : );
91 : }
92 : }
93 :
94 : class _TabsNavigationWrapper extends StatefulWidget {
95 0 : const _TabsNavigationWrapper({
96 : Key? key,
97 : required this.tabs,
98 : required this.tabContentBuilder,
99 : required this.onTabSelected,
100 : required this.selectedIndex,
101 : required this.parameters,
102 : this.appBarParameters,
103 : this.wrapInScaffold = false,
104 0 : }) : super(key: key);
105 :
106 : final List<Widget> tabs;
107 :
108 : final Widget Function(int tabIndex) tabContentBuilder;
109 :
110 : final void Function(int index) onTabSelected;
111 :
112 : final int selectedIndex;
113 :
114 : final TabBarParameters parameters;
115 :
116 : final AppBarParameters? appBarParameters;
117 :
118 : final bool wrapInScaffold;
119 :
120 0 : @override
121 0 : _TabsNavigationWrapperState createState() => _TabsNavigationWrapperState();
122 : }
123 :
124 : class _TabsNavigationWrapperState extends State<_TabsNavigationWrapper>
125 : with TickerProviderStateMixin {
126 : late final TabController _controller;
127 :
128 0 : @override
129 : void initState() {
130 0 : super.initState();
131 0 : _controller = TabController(length: widget.tabs.length, vsync: this);
132 0 : _controller.addListener(_onTabChanged);
133 0 : _controller.animateTo(widget.selectedIndex);
134 : }
135 :
136 0 : @override
137 : void didUpdateWidget(_TabsNavigationWrapper oldWidget) {
138 0 : super.didUpdateWidget(oldWidget);
139 0 : if (oldWidget.selectedIndex != widget.selectedIndex) {
140 0 : _controller.animateTo(widget.selectedIndex);
141 : }
142 : }
143 :
144 0 : @override
145 : void dispose() {
146 0 : _controller.dispose();
147 0 : super.dispose();
148 : }
149 :
150 0 : @override
151 : Widget build(BuildContext context) {
152 0 : final tabBar = TabBar(
153 0 : controller: _controller,
154 0 : tabs: widget.tabs,
155 0 : onTap: (value) => widget.onTabSelected(value),
156 0 : isScrollable: widget.parameters.isScrollable,
157 0 : padding: widget.parameters.padding,
158 0 : indicatorColor: widget.parameters.indicatorColor,
159 : automaticIndicatorColorAdjustment:
160 0 : widget.parameters.automaticIndicatorColorAdjustment,
161 0 : indicatorWeight: widget.parameters.indicatorWeight,
162 0 : indicatorPadding: widget.parameters.indicatorPadding,
163 0 : indicator: widget.parameters.indicator,
164 0 : indicatorSize: widget.parameters.indicatorSize,
165 0 : labelColor: widget.parameters.labelColor,
166 0 : labelStyle: widget.parameters.labelStyle,
167 0 : labelPadding: widget.parameters.labelPadding,
168 0 : unselectedLabelColor: widget.parameters.unselectedLabelColor,
169 0 : unselectedLabelStyle: widget.parameters.unselectedLabelStyle,
170 0 : dragStartBehavior: widget.parameters.dragStartBehavior,
171 0 : overlayColor: widget.parameters.overlayColor,
172 0 : mouseCursor: widget.parameters.mouseCursor,
173 0 : enableFeedback: widget.parameters.enableFeedback,
174 0 : physics: widget.parameters.physics,
175 0 : splashFactory: widget.parameters.splashFactory,
176 0 : splashBorderRadius: widget.parameters.splashBorderRadius,
177 : );
178 0 : final tabBarView = TabBarView(
179 0 : controller: _controller,
180 0 : children: List<Widget>.generate(
181 0 : widget.tabs.length, (index) => widget.tabContentBuilder(index)),
182 : );
183 : Widget result;
184 0 : if (widget.appBarParameters != null) {
185 0 : final appBar = AppBar(
186 : bottom: tabBar,
187 0 : leading: widget.appBarParameters?.leading,
188 : automaticallyImplyLeading:
189 0 : widget.appBarParameters?.automaticallyImplyLeading ?? true,
190 0 : title: widget.appBarParameters?.title,
191 0 : actions: widget.appBarParameters?.actions,
192 0 : flexibleSpace: widget.appBarParameters?.flexibleSpace,
193 0 : elevation: widget.appBarParameters?.elevation,
194 0 : scrolledUnderElevation: widget.appBarParameters?.scrolledUnderElevation,
195 0 : shadowColor: widget.appBarParameters?.shadowColor,
196 0 : surfaceTintColor: widget.appBarParameters?.surfaceTintColor,
197 0 : shape: widget.appBarParameters?.shape,
198 0 : backgroundColor: widget.appBarParameters?.backgroundColor,
199 0 : foregroundColor: widget.appBarParameters?.foregroundColor,
200 0 : iconTheme: widget.appBarParameters?.iconTheme,
201 0 : actionsIconTheme: widget.appBarParameters?.actionsIconTheme,
202 0 : primary: widget.appBarParameters?.primary ?? true,
203 0 : centerTitle: widget.appBarParameters?.centerTitle,
204 : excludeHeaderSemantics:
205 0 : widget.appBarParameters?.excludeHeaderSemantics ?? false,
206 0 : titleSpacing: widget.appBarParameters?.titleSpacing,
207 0 : toolbarOpacity: widget.appBarParameters?.toolbarOpacity ?? 1.0,
208 0 : bottomOpacity: widget.appBarParameters?.bottomOpacity ?? 1.0,
209 0 : toolbarHeight: widget.appBarParameters?.toolbarHeight,
210 0 : leadingWidth: widget.appBarParameters?.leadingWidth,
211 0 : toolbarTextStyle: widget.appBarParameters?.toolbarTextStyle,
212 0 : titleTextStyle: widget.appBarParameters?.titleTextStyle,
213 0 : systemOverlayStyle: widget.appBarParameters?.systemOverlayStyle,
214 : );
215 0 : if (widget.wrapInScaffold) {
216 0 : result = Scaffold(
217 : appBar: appBar,
218 : body: tabBarView,
219 : );
220 : } else {
221 0 : result = Column(
222 0 : children: [
223 : appBar,
224 0 : Expanded(
225 : child: tabBarView,
226 : ),
227 : ],
228 : );
229 : }
230 : } else {
231 0 : result = Column(
232 0 : children: [
233 : tabBar,
234 0 : Expanded(
235 : child: tabBarView,
236 : ),
237 : ],
238 : );
239 0 : if (widget.wrapInScaffold) {
240 0 : result = Scaffold(
241 : body: result,
242 : );
243 : }
244 : }
245 : return result;
246 : }
247 :
248 0 : void _onTabChanged() {
249 0 : widget.onTabSelected(_controller.index);
250 : }
251 : }
252 :
253 : /// Contains parameters to customize the [TabBar].
254 : ///
255 : /// It includes all the same arguments as the [TabBar()], excepting
256 : /// the 'tabs', 'onTap' and 'controller', which are managed by the [TabsNavigationBuilder].
257 : ///
258 : /// See also:
259 : /// - [TabsNavigationBuilder]
260 : /// - [TabBar]
261 : ///
262 : class TabBarParameters {
263 : /// Create a [TabBarParameters] instance.
264 : ///
265 9 : const TabBarParameters({
266 : this.isScrollable = false,
267 : this.padding,
268 : this.indicatorColor,
269 : this.automaticIndicatorColorAdjustment = true,
270 : this.indicatorWeight = 2.0,
271 : this.indicatorPadding = EdgeInsets.zero,
272 : this.indicator,
273 : this.indicatorSize,
274 : this.labelColor,
275 : this.labelStyle,
276 : this.labelPadding,
277 : this.unselectedLabelColor,
278 : this.unselectedLabelStyle,
279 : this.dragStartBehavior = DragStartBehavior.start,
280 : this.overlayColor,
281 : this.mouseCursor,
282 : this.enableFeedback,
283 : this.physics,
284 : this.splashFactory,
285 : this.splashBorderRadius,
286 : });
287 :
288 : /// [TabBar.isScrollable]
289 : ///
290 : final bool isScrollable;
291 :
292 : /// [TabBar.padding]
293 : ///
294 : final EdgeInsetsGeometry? padding;
295 :
296 : /// [TabBar.indicatorColor]
297 : ///
298 : final Color? indicatorColor;
299 :
300 : /// [TabBar.automaticIndicatorColorAdjustment]
301 : ///
302 : final bool automaticIndicatorColorAdjustment;
303 :
304 : /// [TabBar.indicatorWeight]
305 : ///
306 : final double indicatorWeight;
307 :
308 : /// [TabBar.indicatorPadding]
309 : ///
310 : final EdgeInsetsGeometry indicatorPadding;
311 :
312 : /// [TabBar.indicator]
313 : ///
314 : final Decoration? indicator;
315 :
316 : /// [TabBar.indicatorSize]
317 : ///
318 : final TabBarIndicatorSize? indicatorSize;
319 :
320 : /// [TabBar.labelColor]
321 : ///
322 : final Color? labelColor;
323 :
324 : /// [TabBar.labelStyle]
325 : ///
326 : final TextStyle? labelStyle;
327 :
328 : /// [TabBar.labelPadding]
329 : ///
330 : final EdgeInsetsGeometry? labelPadding;
331 :
332 : /// [TabBar.unselectedLabelColor]
333 : ///
334 : final Color? unselectedLabelColor;
335 :
336 : /// [TabBar.unselectedLabelStyle]
337 : ///
338 : final TextStyle? unselectedLabelStyle;
339 :
340 : /// [TabBar.dragStartBehavior]
341 : ///
342 : final DragStartBehavior dragStartBehavior;
343 :
344 : /// [TabBar.overlayColor]
345 : ///
346 : final MaterialStateProperty<Color?>? overlayColor;
347 :
348 : /// [TabBar.mouseCursor]
349 : ///
350 : final MouseCursor? mouseCursor;
351 :
352 : /// [TabBar.enableFeedback]
353 : ///
354 : final bool? enableFeedback;
355 :
356 : /// [TabBar.physics]
357 : ///
358 : final ScrollPhysics? physics;
359 :
360 : /// [TabBar.splashFactory]
361 : ///
362 : final InteractiveInkFeatureFactory? splashFactory;
363 :
364 : /// [TabBar.splashBorderRadius]
365 : ///
366 : final BorderRadius? splashBorderRadius;
367 : }
368 :
369 : /// Contains parameters to customize the [AppBar].
370 : ///
371 : /// It includes all the same arguments as the [AppBar()], excepting
372 : /// the 'bottom' which is managed by the [TabsNavigationBuilder].
373 : ///
374 : /// See also:
375 : /// - [TabsNavigationBuilder]
376 : /// - [AppBar]
377 : ///
378 : class AppBarParameters {
379 : /// Create a [AppBarParameters] instance.
380 : ///
381 0 : const AppBarParameters({
382 : this.leading,
383 : this.automaticallyImplyLeading = true,
384 : this.title,
385 : this.actions,
386 : this.flexibleSpace,
387 : this.elevation,
388 : this.scrolledUnderElevation,
389 : this.shadowColor,
390 : this.surfaceTintColor,
391 : this.shape,
392 : this.backgroundColor,
393 : this.foregroundColor,
394 : this.iconTheme,
395 : this.actionsIconTheme,
396 : this.primary = true,
397 : this.centerTitle,
398 : this.excludeHeaderSemantics = false,
399 : this.titleSpacing,
400 : this.toolbarOpacity = 1.0,
401 : this.bottomOpacity = 1.0,
402 : this.toolbarHeight,
403 : this.leadingWidth,
404 : this.toolbarTextStyle,
405 : this.titleTextStyle,
406 : this.systemOverlayStyle,
407 : });
408 :
409 : /// [AppBar.leading]
410 : ///
411 : final Widget? leading;
412 :
413 : /// [AppBar.leading]
414 : ///
415 : final bool automaticallyImplyLeading;
416 :
417 : /// [AppBar.title]
418 : ///
419 : final Widget? title;
420 :
421 : /// [AppBar.actions]
422 : ///
423 : final List<Widget>? actions;
424 :
425 : /// [AppBar.flexibleSpace]
426 : ///
427 : final Widget? flexibleSpace;
428 :
429 : /// [AppBar.elevation]
430 : ///
431 : final double? elevation;
432 :
433 : /// [AppBar.scrolledUnderElevation]
434 : ///
435 : final double? scrolledUnderElevation;
436 :
437 : /// [AppBar.shadowColor]
438 : ///
439 : final Color? shadowColor;
440 :
441 : /// [AppBar.surfaceTintColor]
442 : ///
443 : final Color? surfaceTintColor;
444 :
445 : /// [AppBar.shape]
446 : ///
447 : final ShapeBorder? shape;
448 :
449 : /// [AppBar.backgroundColor]
450 : ///
451 : final Color? backgroundColor;
452 :
453 : /// [AppBar.foregroundColor]
454 : ///
455 : final Color? foregroundColor;
456 :
457 : /// [AppBar.iconTheme]
458 : ///
459 : final IconThemeData? iconTheme;
460 :
461 : /// [AppBar.actionsIconTheme]
462 : ///
463 : final IconThemeData? actionsIconTheme;
464 :
465 : /// [AppBar.primary]
466 : ///
467 : final bool primary;
468 :
469 : /// [AppBar.centerTitle]
470 : ///
471 : final bool? centerTitle;
472 :
473 : /// [AppBar.excludeHeaderSemantics]
474 : ///
475 : final bool excludeHeaderSemantics;
476 :
477 : /// [AppBar.titleSpacing]
478 : ///
479 : final double? titleSpacing;
480 :
481 : /// [AppBar.toolbarOpacity]
482 : ///
483 : final double toolbarOpacity;
484 :
485 : /// [AppBar.bottomOpacity]
486 : ///
487 : final double bottomOpacity;
488 :
489 : /// [AppBar.toolbarHeight]
490 : ///
491 : final double? toolbarHeight;
492 :
493 : /// [AppBar.leadingWidth]
494 : ///
495 : final double? leadingWidth;
496 :
497 : /// [AppBar.toolbarTextStyle]
498 : ///
499 : final TextStyle? toolbarTextStyle;
500 :
501 : /// [AppBar.titleTextStyle]
502 : ///
503 : final TextStyle? titleTextStyle;
504 :
505 : /// [AppBar.systemOverlayStyle]
506 : ///
507 : final SystemUiOverlayStyle? systemOverlayStyle;
508 : }
|