LCOV - code coverage report
Current view: top level - src/widgets - tabs_navigation_builder.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 1 111 0.9 %
Date: 2023-03-02 15:31:08 Functions: 0 0 -

          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 extends 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           0 :   }) : super();
      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(
      67             :       BuildContext context, Destination destination)? 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 :       tabDestination: (tabIndex) => navigator.destinations[tabIndex],
      84           0 :       onTabSelected: (index) => navigator.goTo(navigator.destinations[index]),
      85           0 :       selectedIndex: navigator.destinations.indexOf(currentDestination),
      86           0 :       parameters: parameters,
      87             :       appBarParameters:
      88           0 :           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.tabDestination,
      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 Destination Function(int tabIndex) tabDestination;
     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             :   final _content = <Widget>[];
     127             : 
     128             :   late final TabController _controller;
     129             : 
     130           0 :   @override
     131             :   void initState() {
     132           0 :     super.initState();
     133           0 :     _content.addAll(List<Widget>.generate(
     134           0 :         widget.tabs.length,
     135           0 :         (index) => _TabDestinationContentWrapper(
     136           0 :               keepState: widget.tabDestination(index).isFinalDestination,
     137           0 :               child: widget.tabDestination(index).build(context),
     138             :             )));
     139           0 :     _controller = TabController(length: widget.tabs.length, vsync: this);
     140           0 :     _controller.addListener(_onTabChanged);
     141           0 :     _controller.animateTo(widget.selectedIndex);
     142             :   }
     143             : 
     144           0 :   @override
     145             :   void didUpdateWidget(_TabsNavigationWrapper oldWidget) {
     146           0 :     super.didUpdateWidget(oldWidget);
     147           0 :     if (oldWidget.selectedIndex != widget.selectedIndex) {
     148           0 :       _controller.animateTo(widget.selectedIndex);
     149             :     }
     150             :   }
     151             : 
     152           0 :   @override
     153             :   void dispose() {
     154           0 :     _controller.dispose();
     155           0 :     super.dispose();
     156             :   }
     157             : 
     158           0 :   @override
     159             :   Widget build(BuildContext context) {
     160           0 :     final tabBar = TabBar(
     161           0 :       controller: _controller,
     162           0 :       tabs: widget.tabs,
     163           0 :       onTap: (value) => widget.onTabSelected(value),
     164           0 :       isScrollable: widget.parameters.isScrollable,
     165           0 :       padding: widget.parameters.padding,
     166           0 :       indicatorColor: widget.parameters.indicatorColor,
     167             :       automaticIndicatorColorAdjustment:
     168           0 :           widget.parameters.automaticIndicatorColorAdjustment,
     169           0 :       indicatorWeight: widget.parameters.indicatorWeight,
     170           0 :       indicatorPadding: widget.parameters.indicatorPadding,
     171           0 :       indicator: widget.parameters.indicator,
     172           0 :       indicatorSize: widget.parameters.indicatorSize,
     173           0 :       labelColor: widget.parameters.labelColor,
     174           0 :       labelStyle: widget.parameters.labelStyle,
     175           0 :       labelPadding: widget.parameters.labelPadding,
     176           0 :       unselectedLabelColor: widget.parameters.unselectedLabelColor,
     177           0 :       unselectedLabelStyle: widget.parameters.unselectedLabelStyle,
     178           0 :       dragStartBehavior: widget.parameters.dragStartBehavior,
     179           0 :       overlayColor: widget.parameters.overlayColor,
     180           0 :       mouseCursor: widget.parameters.mouseCursor,
     181           0 :       enableFeedback: widget.parameters.enableFeedback,
     182           0 :       physics: widget.parameters.physics,
     183           0 :       splashFactory: widget.parameters.splashFactory,
     184           0 :       splashBorderRadius: widget.parameters.splashBorderRadius,
     185             :     );
     186           0 :     final tabBarView = TabBarView(
     187           0 :       controller: _controller,
     188           0 :       children: _content,
     189             :     );
     190             :     Widget result;
     191           0 :     if (widget.appBarParameters != null) {
     192           0 :       final appBar = AppBar(
     193             :         bottom: tabBar,
     194           0 :         leading: widget.appBarParameters?.leading,
     195             :         automaticallyImplyLeading:
     196           0 :             widget.appBarParameters?.automaticallyImplyLeading ?? true,
     197           0 :         title: widget.appBarParameters?.title,
     198           0 :         actions: widget.appBarParameters?.actions,
     199           0 :         flexibleSpace: widget.appBarParameters?.flexibleSpace,
     200           0 :         elevation: widget.appBarParameters?.elevation,
     201           0 :         scrolledUnderElevation: widget.appBarParameters?.scrolledUnderElevation,
     202           0 :         shadowColor: widget.appBarParameters?.shadowColor,
     203           0 :         surfaceTintColor: widget.appBarParameters?.surfaceTintColor,
     204           0 :         shape: widget.appBarParameters?.shape,
     205           0 :         backgroundColor: widget.appBarParameters?.backgroundColor,
     206           0 :         foregroundColor: widget.appBarParameters?.foregroundColor,
     207           0 :         iconTheme: widget.appBarParameters?.iconTheme,
     208           0 :         actionsIconTheme: widget.appBarParameters?.actionsIconTheme,
     209           0 :         primary: widget.appBarParameters?.primary ?? true,
     210           0 :         centerTitle: widget.appBarParameters?.centerTitle,
     211             :         excludeHeaderSemantics:
     212           0 :             widget.appBarParameters?.excludeHeaderSemantics ?? false,
     213           0 :         titleSpacing: widget.appBarParameters?.titleSpacing,
     214           0 :         toolbarOpacity: widget.appBarParameters?.toolbarOpacity ?? 1.0,
     215           0 :         bottomOpacity: widget.appBarParameters?.bottomOpacity ?? 1.0,
     216           0 :         toolbarHeight: widget.appBarParameters?.toolbarHeight,
     217           0 :         leadingWidth: widget.appBarParameters?.leadingWidth,
     218           0 :         toolbarTextStyle: widget.appBarParameters?.toolbarTextStyle,
     219           0 :         titleTextStyle: widget.appBarParameters?.titleTextStyle,
     220           0 :         systemOverlayStyle: widget.appBarParameters?.systemOverlayStyle,
     221             :       );
     222           0 :       if (widget.wrapInScaffold) {
     223           0 :         result = Scaffold(
     224             :           appBar: appBar,
     225             :           body: tabBarView,
     226             :         );
     227             :       } else {
     228           0 :         result = Column(
     229           0 :           children: [
     230             :             appBar,
     231           0 :             Expanded(
     232             :               child: tabBarView,
     233             :             ),
     234             :           ],
     235             :         );
     236             :       }
     237             :     } else {
     238           0 :       result = Column(
     239           0 :         children: [
     240             :           tabBar,
     241           0 :           Expanded(
     242             :             child: tabBarView,
     243             :           ),
     244             :         ],
     245             :       );
     246           0 :       if (widget.wrapInScaffold) {
     247           0 :         result = Scaffold(
     248             :           body: result,
     249             :         );
     250             :       }
     251             :     }
     252             :     return result;
     253             :   }
     254             : 
     255           0 :   void _onTabChanged() {
     256           0 :     widget.onTabSelected(_controller.index);
     257             :   }
     258             : }
     259             : 
     260             : class _TabDestinationContentWrapper extends StatefulWidget {
     261           0 :   const _TabDestinationContentWrapper({
     262             :     Key? key,
     263             :     required this.keepState,
     264             :     required this.child,
     265           0 :   }) : super(key: key);
     266             : 
     267             :   final bool keepState;
     268             : 
     269             :   final Widget child;
     270             : 
     271           0 :   @override
     272             :   State<_TabDestinationContentWrapper> createState() =>
     273           0 :       _TabDestinationContentWrapperState();
     274             : }
     275             : 
     276             : class _TabDestinationContentWrapperState
     277             :     extends State<_TabDestinationContentWrapper>
     278             :     with AutomaticKeepAliveClientMixin<_TabDestinationContentWrapper> {
     279           0 :   @override
     280           0 :   bool get wantKeepAlive => widget.keepState;
     281             : 
     282           0 :   @override
     283             :   Widget build(BuildContext context) {
     284           0 :     super.build(context);
     285           0 :     return widget.child;
     286             :   }
     287             : }
     288             : 
     289             : /// Contains parameters to customize the [TabBar].
     290             : ///
     291             : /// It includes all the same arguments as the [TabBar()], excepting
     292             : /// the 'tabs', 'onTap' and 'controller', which are managed by the [TabsNavigationBuilder].
     293             : ///
     294             : /// See also:
     295             : /// - [TabsNavigationBuilder]
     296             : /// - [TabBar]
     297             : ///
     298             : class TabBarParameters {
     299             :   /// Create a [TabBarParameters] instance.
     300             :   ///
     301          11 :   const TabBarParameters({
     302             :     this.isScrollable = false,
     303             :     this.padding,
     304             :     this.indicatorColor,
     305             :     this.automaticIndicatorColorAdjustment = true,
     306             :     this.indicatorWeight = 2.0,
     307             :     this.indicatorPadding = EdgeInsets.zero,
     308             :     this.indicator,
     309             :     this.indicatorSize,
     310             :     this.labelColor,
     311             :     this.labelStyle,
     312             :     this.labelPadding,
     313             :     this.unselectedLabelColor,
     314             :     this.unselectedLabelStyle,
     315             :     this.dragStartBehavior = DragStartBehavior.start,
     316             :     this.overlayColor,
     317             :     this.mouseCursor,
     318             :     this.enableFeedback,
     319             :     this.physics,
     320             :     this.splashFactory,
     321             :     this.splashBorderRadius,
     322             :   });
     323             : 
     324             :   /// [TabBar.isScrollable]
     325             :   ///
     326             :   final bool isScrollable;
     327             : 
     328             :   /// [TabBar.padding]
     329             :   ///
     330             :   final EdgeInsetsGeometry? padding;
     331             : 
     332             :   /// [TabBar.indicatorColor]
     333             :   ///
     334             :   final Color? indicatorColor;
     335             : 
     336             :   /// [TabBar.automaticIndicatorColorAdjustment]
     337             :   ///
     338             :   final bool automaticIndicatorColorAdjustment;
     339             : 
     340             :   /// [TabBar.indicatorWeight]
     341             :   ///
     342             :   final double indicatorWeight;
     343             : 
     344             :   /// [TabBar.indicatorPadding]
     345             :   ///
     346             :   final EdgeInsetsGeometry indicatorPadding;
     347             : 
     348             :   /// [TabBar.indicator]
     349             :   ///
     350             :   final Decoration? indicator;
     351             : 
     352             :   /// [TabBar.indicatorSize]
     353             :   ///
     354             :   final TabBarIndicatorSize? indicatorSize;
     355             : 
     356             :   /// [TabBar.labelColor]
     357             :   ///
     358             :   final Color? labelColor;
     359             : 
     360             :   /// [TabBar.labelStyle]
     361             :   ///
     362             :   final TextStyle? labelStyle;
     363             : 
     364             :   /// [TabBar.labelPadding]
     365             :   ///
     366             :   final EdgeInsetsGeometry? labelPadding;
     367             : 
     368             :   /// [TabBar.unselectedLabelColor]
     369             :   ///
     370             :   final Color? unselectedLabelColor;
     371             : 
     372             :   /// [TabBar.unselectedLabelStyle]
     373             :   ///
     374             :   final TextStyle? unselectedLabelStyle;
     375             : 
     376             :   /// [TabBar.dragStartBehavior]
     377             :   ///
     378             :   final DragStartBehavior dragStartBehavior;
     379             : 
     380             :   /// [TabBar.overlayColor]
     381             :   ///
     382             :   final MaterialStateProperty<Color?>? overlayColor;
     383             : 
     384             :   /// [TabBar.mouseCursor]
     385             :   ///
     386             :   final MouseCursor? mouseCursor;
     387             : 
     388             :   /// [TabBar.enableFeedback]
     389             :   ///
     390             :   final bool? enableFeedback;
     391             : 
     392             :   /// [TabBar.physics]
     393             :   ///
     394             :   final ScrollPhysics? physics;
     395             : 
     396             :   /// [TabBar.splashFactory]
     397             :   ///
     398             :   final InteractiveInkFeatureFactory? splashFactory;
     399             : 
     400             :   /// [TabBar.splashBorderRadius]
     401             :   ///
     402             :   final BorderRadius? splashBorderRadius;
     403             : }
     404             : 
     405             : /// Contains parameters to customize the [AppBar].
     406             : ///
     407             : /// It includes all the same arguments as the [AppBar()], excepting
     408             : /// the 'bottom' which is managed by the [TabsNavigationBuilder].
     409             : ///
     410             : /// See also:
     411             : /// - [TabsNavigationBuilder]
     412             : /// - [AppBar]
     413             : ///
     414             : class AppBarParameters {
     415             :   /// Create a [AppBarParameters] instance.
     416             :   ///
     417           0 :   const AppBarParameters({
     418             :     this.leading,
     419             :     this.automaticallyImplyLeading = true,
     420             :     this.title,
     421             :     this.actions,
     422             :     this.flexibleSpace,
     423             :     this.elevation,
     424             :     this.scrolledUnderElevation,
     425             :     this.shadowColor,
     426             :     this.surfaceTintColor,
     427             :     this.shape,
     428             :     this.backgroundColor,
     429             :     this.foregroundColor,
     430             :     this.iconTheme,
     431             :     this.actionsIconTheme,
     432             :     this.primary = true,
     433             :     this.centerTitle,
     434             :     this.excludeHeaderSemantics = false,
     435             :     this.titleSpacing,
     436             :     this.toolbarOpacity = 1.0,
     437             :     this.bottomOpacity = 1.0,
     438             :     this.toolbarHeight,
     439             :     this.leadingWidth,
     440             :     this.toolbarTextStyle,
     441             :     this.titleTextStyle,
     442             :     this.systemOverlayStyle,
     443             :   });
     444             : 
     445             :   /// [AppBar.leading]
     446             :   ///
     447             :   final Widget? leading;
     448             : 
     449             :   /// [AppBar.leading]
     450             :   ///
     451             :   final bool automaticallyImplyLeading;
     452             : 
     453             :   /// [AppBar.title]
     454             :   ///
     455             :   final Widget? title;
     456             : 
     457             :   /// [AppBar.actions]
     458             :   ///
     459             :   final List<Widget>? actions;
     460             : 
     461             :   /// [AppBar.flexibleSpace]
     462             :   ///
     463             :   final Widget? flexibleSpace;
     464             : 
     465             :   /// [AppBar.elevation]
     466             :   ///
     467             :   final double? elevation;
     468             : 
     469             :   /// [AppBar.scrolledUnderElevation]
     470             :   ///
     471             :   final double? scrolledUnderElevation;
     472             : 
     473             :   /// [AppBar.shadowColor]
     474             :   ///
     475             :   final Color? shadowColor;
     476             : 
     477             :   /// [AppBar.surfaceTintColor]
     478             :   ///
     479             :   final Color? surfaceTintColor;
     480             : 
     481             :   /// [AppBar.shape]
     482             :   ///
     483             :   final ShapeBorder? shape;
     484             : 
     485             :   /// [AppBar.backgroundColor]
     486             :   ///
     487             :   final Color? backgroundColor;
     488             : 
     489             :   /// [AppBar.foregroundColor]
     490             :   ///
     491             :   final Color? foregroundColor;
     492             : 
     493             :   /// [AppBar.iconTheme]
     494             :   ///
     495             :   final IconThemeData? iconTheme;
     496             : 
     497             :   /// [AppBar.actionsIconTheme]
     498             :   ///
     499             :   final IconThemeData? actionsIconTheme;
     500             : 
     501             :   /// [AppBar.primary]
     502             :   ///
     503             :   final bool primary;
     504             : 
     505             :   /// [AppBar.centerTitle]
     506             :   ///
     507             :   final bool? centerTitle;
     508             : 
     509             :   /// [AppBar.excludeHeaderSemantics]
     510             :   ///
     511             :   final bool excludeHeaderSemantics;
     512             : 
     513             :   /// [AppBar.titleSpacing]
     514             :   ///
     515             :   final double? titleSpacing;
     516             : 
     517             :   /// [AppBar.toolbarOpacity]
     518             :   ///
     519             :   final double toolbarOpacity;
     520             : 
     521             :   /// [AppBar.bottomOpacity]
     522             :   ///
     523             :   final double bottomOpacity;
     524             : 
     525             :   /// [AppBar.toolbarHeight]
     526             :   ///
     527             :   final double? toolbarHeight;
     528             : 
     529             :   /// [AppBar.leadingWidth]
     530             :   ///
     531             :   final double? leadingWidth;
     532             : 
     533             :   /// [AppBar.toolbarTextStyle]
     534             :   ///
     535             :   final TextStyle? toolbarTextStyle;
     536             : 
     537             :   /// [AppBar.titleTextStyle]
     538             :   ///
     539             :   final TextStyle? titleTextStyle;
     540             : 
     541             :   /// [AppBar.systemOverlayStyle]
     542             :   ///
     543             :   final SystemUiOverlayStyle? systemOverlayStyle;
     544             : }

Generated by: LCOV version