LCOV - code coverage report
Current view: top level - src - bar.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 102 103 99.0 %
Date: 2020-02-05 17:16:54 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:math' as math;
       2             : 
       3             : import 'package:flutter/cupertino.dart';
       4             : import 'package:flutter/material.dart';
       5             : import 'package:flutter/widgets.dart';
       6             : 
       7             : import 'chip_builder.dart';
       8             : import 'item.dart';
       9             : import 'painter.dart';
      10             : import 'style/fixed_circle_tab_style.dart';
      11             : import 'style/fixed_tab_style.dart';
      12             : import 'style/react_circle_tab_style.dart';
      13             : import 'style/react_tab_style.dart';
      14             : import 'style/styles.dart';
      15             : 
      16             : /// Default size of the curve line
      17             : const double CONVEX_SIZE = 80;
      18             : 
      19             : /// Default height of the AppBar
      20             : const double BAR_HEIGHT = 50;
      21             : 
      22             : /// Default distance that the child's top edge is inset from the top of the stack.
      23             : const double CURVE_TOP = -25;
      24             : 
      25             : const double ACTION_LAYOUT_SIZE = 60;
      26             : const double ACTION_INNER_BUTTON_SIZE = 40;
      27             : const int CURVE_INDEX = -1;
      28             : const double ELEVATION = 2;
      29             : 
      30           1 : enum TabStyle {
      31             :   /// convex shape fixed center, see [FixedTabStyle]
      32             :   ///
      33             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-fixed.gif)
      34           1 :   fixed,
      35             : 
      36             :   /// convex shape is fixed center with circle, see [FixedCircleTabStyle]
      37             :   ///
      38             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-fixed-circle.gif)
      39           1 :   fixedCircle,
      40             : 
      41             :   /// convex shape is moved after selection, see [ReactTabStyle]
      42             :   ///
      43             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-react.gif)
      44           1 :   react,
      45             : 
      46             :   /// convex shape is moved with circle after selection, see [ReactCircleTabStyle]
      47             :   ///
      48             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-react-circle.gif)
      49           1 :   reactCircle,
      50             : 
      51             :   /// tab icon, text animated with pop transition
      52             :   ///
      53             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-textIn.gif)
      54           1 :   textIn,
      55             : 
      56             :   /// similar to [TabStyle.textIn], text first
      57             :   ///
      58             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-titled.gif)
      59           1 :   titled,
      60             : 
      61             :   /// tab item is flipped when selected, does not support [flutter web]
      62             :   ///
      63             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-flip.gif)
      64           1 :   flip,
      65             : 
      66             :   /// user defined style
      67           1 :   custom,
      68             : }
      69             : 
      70             : /// Online example can be found at http://hacktons.cn/convex_bottom_bar
      71             : ///
      72             : /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-theming.png)
      73             : class ConvexAppBar extends StatefulWidget {
      74             :   /// TAB item builder
      75             :   final DelegateBuilder itemBuilder;
      76             : 
      77             :   final ChipBuilder chipBuilder;
      78             : 
      79             :   /// Tab Click handler
      80             :   final GestureTapIndexCallback onTap;
      81             : 
      82             :   /// Color of the AppBar
      83             :   final Color backgroundColor;
      84             : 
      85             :   /// If provided, backgroundColor for tab app will be ignored
      86             :   ///
      87             :   /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-gradient.gif)
      88             :   final Gradient gradient;
      89             : 
      90             :   /// Tab count
      91             :   final int count;
      92             : 
      93             :   /// Height of the AppBar
      94             :   final double height;
      95             : 
      96             :   /// Size of the curve line
      97             :   final double curveSize;
      98             : 
      99             :   /// The distance that the [actionButton] top edge is inset from the top of the AppBar.
     100             :   final double top;
     101             : 
     102             :   /// Elevation for the bar top edge
     103             :   final double elevation;
     104             : 
     105             :   /// Style to describe the convex shape
     106             :   final TabStyle style;
     107             : 
     108             :   /// The curve to use in the forward direction. Only works when tab style is not fixed.
     109             :   final Curve curve;
     110             : 
     111             :   /// Construct a new appbar with internal style
     112             :   ///
     113             :   /// {@tool sample}
     114             :   ///
     115             :   /// ```dart
     116             :   /// ConvexAppBar(
     117             :   ///   items: [
     118             :   ///     TabItem(title: 'Tab A', icon: Icons.add),
     119             :   ///     TabItem(title: 'Tab B', icon: Icons.near_me),
     120             :   ///     TabItem(title: 'Tab C', icon: Icons.web),
     121             :   ///   ],
     122             :   /// )
     123             :   /// ```
     124             :   /// {@end-tool}
     125           1 :   ConvexAppBar({
     126             :     Key key,
     127             :     @required List<TabItem> items,
     128             :     GestureTapIndexCallback onTap,
     129             :     Color color,
     130             :     Color activeColor,
     131             :     Color backgroundColor,
     132             :     Gradient gradient,
     133             :     double height,
     134             :     double curveSize,
     135             :     double top,
     136             :     double elevation,
     137             :     TabStyle style = TabStyle.reactCircle,
     138             :     Curve curve = Curves.easeInOut,
     139             :     ChipBuilder chipBuilder,
     140           1 :   }) : this.builder(
     141             :           key: key,
     142           1 :           itemBuilder: supportedStyle(
     143             :             style,
     144             :             items: items,
     145             :             color: color ?? Colors.white60,
     146             :             activeColor: activeColor ?? Colors.white,
     147             :             backgroundColor: backgroundColor ?? Colors.blue,
     148             :             curve: curve ?? Curves.easeInOut,
     149             :           ),
     150             :           onTap: onTap,
     151             :           backgroundColor: backgroundColor ?? Colors.blue,
     152           1 :           count: items.length,
     153             :           gradient: gradient,
     154             :           height: height,
     155             :           curveSize: curveSize,
     156             :           top: top,
     157             :           elevation: elevation,
     158             :           style: style,
     159             :           curve: curve ?? Curves.easeInOut,
     160             :           chipBuilder: chipBuilder,
     161             :         );
     162             : 
     163             :   /// define a custom tab style by implement a [DelegateBuilder]
     164           1 :   const ConvexAppBar.builder({
     165             :     Key key,
     166             :     @required this.itemBuilder,
     167             :     @required this.count,
     168             :     this.onTap,
     169             :     this.backgroundColor,
     170             :     this.gradient,
     171             :     this.height,
     172             :     this.curveSize,
     173             :     this.top,
     174             :     this.elevation,
     175             :     this.style = TabStyle.reactCircle,
     176             :     this.curve = Curves.easeInOut,
     177             :     this.chipBuilder,
     178           1 :   })  : assert(top == null || top <= 0, 'top should be negative'),
     179           0 :         assert(itemBuilder != null, 'provide custom buidler'),
     180           1 :         super(key: key);
     181             : 
     182             :   /// Construct a new appbar with badge
     183             :   ///
     184             :   /// {@animation 1010 598 https://github.com/hacktons/convex_bottom_bar/raw/master/doc/badge-demo.mp4}
     185             :   ///
     186             :   /// [badge] is map with tab items, the value of entry can be either [String],
     187             :   /// [IconData], [Color] or [Widget].
     188             :   ///
     189             :   /// {@tool sample}
     190             :   ///
     191             :   /// ```dart
     192             :   /// ConvexAppBar.badge(
     193             :   ///   {3: '99+'},
     194             :   ///   items: [
     195             :   ///     TabItem(title: 'Tab A', icon: Icons.add),
     196             :   ///     TabItem(title: 'Tab B', icon: Icons.near_me),
     197             :   ///     TabItem(title: 'Tab C', icon: Icons.web),
     198             :   ///   ],
     199             :   /// )
     200             :   /// ```
     201             :   /// {@end-tool}
     202           1 :   factory ConvexAppBar.badge(
     203             :     Map<int, dynamic> badge, {
     204             :     Key key,
     205             :     // config for badge
     206             :     Color badgeTextColor,
     207             :     Color badgeColor,
     208             :     EdgeInsets badgePadding,
     209             :     double badgeBorderRadius,
     210             :     // parameter for appbar
     211             :     List<TabItem> items,
     212             :     GestureTapIndexCallback onTap,
     213             :     Color color,
     214             :     Color activeColor,
     215             :     Color backgroundColor,
     216             :     Gradient gradient,
     217             :     double height,
     218             :     double curveSize,
     219             :     double top,
     220             :     double elevation,
     221             :     TabStyle style,
     222             :     Curve curve,
     223             :   }) {
     224             :     DefaultChipBuilder chipBuilder;
     225           1 :     if (badge != null && badge.isNotEmpty) {
     226           1 :       chipBuilder = DefaultChipBuilder(
     227             :         badge,
     228             :         textColor: badgeTextColor,
     229             :         badgeColor: badgeColor,
     230             :         padding: badgePadding,
     231             :         borderRadius: badgeBorderRadius,
     232             :       );
     233             :     }
     234           1 :     return ConvexAppBar(
     235             :       items: items,
     236             :       key: key,
     237             :       onTap: onTap,
     238             :       color: color,
     239             :       activeColor: activeColor,
     240             :       backgroundColor: backgroundColor,
     241             :       gradient: gradient,
     242             :       height: height,
     243             :       curveSize: curveSize,
     244             :       top: top,
     245             :       elevation: elevation,
     246             :       style: style,
     247             :       curve: curve,
     248             :       chipBuilder: chipBuilder,
     249             :     );
     250             :   }
     251             : 
     252           1 :   @override
     253             :   _State createState() {
     254           1 :     return _State();
     255             :   }
     256             : }
     257             : 
     258             : /// Item builder
     259             : abstract class DelegateBuilder {
     260             :   /// called when the tab item is build
     261             :   Widget build(BuildContext context, int index, bool active);
     262             : 
     263             :   /// whether the convex shape is fixed center or positioned according to selection
     264           1 :   bool fixed() {
     265             :     return false;
     266             :   }
     267             : }
     268             : 
     269             : class _State extends State<ConvexAppBar> with TickerProviderStateMixin {
     270             :   int _currentSelectedIndex = 0;
     271             :   Animation<double> _animation;
     272             :   AnimationController _controller;
     273             : 
     274           1 :   @override
     275             :   void initState() {
     276           1 :     if (!isFixed()) {
     277           1 :       _initAnimation();
     278             :     }
     279           1 :     super.initState();
     280             :   }
     281             : 
     282           1 :   Animation<double> _initAnimation({int from, int to}) {
     283           1 :     if (from != null && (from == to)) {
     284           1 :       return _animation;
     285             :     }
     286             :     from ??= 0;
     287             :     to ??= from;
     288           6 :     var lower = (2 * from + 1) / (2 * widget.count);
     289           6 :     var upper = (2 * to + 1) / (2 * widget.count);
     290           2 :     _controller = AnimationController(
     291           1 :       duration: Duration(milliseconds: 150),
     292             :       vsync: this,
     293             :     );
     294           1 :     final Animation curve = CurvedAnimation(
     295           1 :       parent: _controller,
     296           2 :       curve: widget.curve,
     297             :     );
     298           3 :     _animation = Tween(begin: lower, end: upper).animate(curve);
     299           1 :     return _animation;
     300             :   }
     301             : 
     302           1 :   @override
     303             :   void dispose() {
     304           2 :     _controller?.dispose();
     305           1 :     super.dispose();
     306             :   }
     307             : 
     308           1 :   @override
     309             :   Widget build(BuildContext context) {
     310             :     // take care of iPhoneX' safe area at bottom edge
     311             :     final double additionalBottomPadding =
     312           4 :         math.max(MediaQuery.of(context).padding.bottom, 0.0);
     313           3 :     var halfSize = widget.count ~/ 2;
     314           2 :     final convexIndex = isFixed() ? halfSize : _currentSelectedIndex;
     315           3 :     final active = isFixed() ? convexIndex == _currentSelectedIndex : true;
     316           1 :     return Stack(
     317             :       overflow: Overflow.visible,
     318             :       alignment: Alignment.bottomCenter,
     319           1 :       children: <Widget>[
     320           1 :         Container(
     321           3 :           height: widget.height ?? BAR_HEIGHT + additionalBottomPadding,
     322           3 :           width: MediaQuery.of(context).size.width,
     323           1 :           child: CustomPaint(
     324           1 :             painter: ConvexPainter(
     325           2 :               top: widget.top ?? CURVE_TOP,
     326           2 :               width: widget.curveSize ?? CONVEX_SIZE,
     327           2 :               height: widget.curveSize ?? CONVEX_SIZE,
     328           2 :               color: widget.backgroundColor ?? Colors.blue,
     329           2 :               gradient: widget.gradient,
     330           2 :               sigma: widget.elevation ?? ELEVATION,
     331           1 :               leftPercent: isFixed()
     332             :                   ? const AlwaysStoppedAnimation<double>(0.5)
     333           1 :                   : _animation ?? _initAnimation(),
     334             :             ),
     335             :           ),
     336             :         ),
     337           1 :         barContent(additionalBottomPadding),
     338           1 :         Positioned.fill(
     339           2 :           top: widget.top,
     340             :           bottom: additionalBottomPadding,
     341           1 :           child: FractionallySizedBox(
     342           3 :               widthFactor: 1 / widget.count,
     343           3 :               alignment: Alignment((convexIndex - halfSize) / (halfSize), 0),
     344           1 :               child: GestureDetector(
     345           1 :                 child: _newTab(convexIndex, active),
     346           1 :                 onTap: () {
     347           1 :                   _onTabClick(convexIndex);
     348           2 :                   setState(() {
     349           1 :                     _currentSelectedIndex = convexIndex;
     350             :                   });
     351             :                 },
     352             :               )),
     353             :         ),
     354             :       ],
     355             :     );
     356             :   }
     357             : 
     358           4 :   bool isFixed() => widget.itemBuilder.fixed();
     359             : 
     360           1 :   Widget barContent(double paddingBottom) {
     361           1 :     List<Widget> children = [];
     362             :     // add placeholder Widget
     363           5 :     var curveTabIndex = isFixed() ? widget.count ~/ 2 : _currentSelectedIndex;
     364           4 :     for (var i = 0; i < widget.count; i++) {
     365           1 :       if (i == curveTabIndex) {
     366           3 :         children.add(Expanded(child: Container()));
     367             :         continue;
     368             :       }
     369           2 :       var active = _currentSelectedIndex == i;
     370           1 :       Widget child = _newTab(i, active);
     371           2 :       children.add(Expanded(
     372           1 :           child: GestureDetector(
     373             :         behavior: HitTestBehavior.opaque,
     374             :         child: child,
     375           1 :         onTap: () {
     376           1 :           _onTabClick(i);
     377           2 :           setState(() {
     378           1 :             _currentSelectedIndex = i;
     379             :           });
     380             :         },
     381             :       )));
     382             :     }
     383             : 
     384           1 :     return Container(
     385           3 :       height: widget.height ?? BAR_HEIGHT + paddingBottom,
     386           1 :       padding: EdgeInsets.only(bottom: paddingBottom),
     387           1 :       child: Row(
     388             :         mainAxisSize: MainAxisSize.max,
     389             :         crossAxisAlignment: CrossAxisAlignment.center,
     390             :         children: children,
     391             :       ),
     392             :     );
     393             :   }
     394             : 
     395           1 :   Widget _newTab(int i, bool active) {
     396           4 :     var child = widget.itemBuilder.build(context, i, active);
     397           2 :     if (widget.chipBuilder != null) {
     398           4 :       child = widget.chipBuilder.build(context, child, i, active);
     399             :     }
     400             :     return child;
     401             :   }
     402             : 
     403           1 :   void _onTabClick(int i) {
     404           2 :     _initAnimation(from: _currentSelectedIndex, to: i);
     405           2 :     _controller?.forward();
     406           2 :     if (widget.onTap != null) {
     407           2 :       widget.onTap(i);
     408             :     }
     409             :   }
     410             : }
     411             : 
     412             : typedef GestureTapIndexCallback = void Function(int index);
     413             : typedef CustomTabBuilder = Widget Function(
     414             :     BuildContext context, int index, bool active);

Generated by: LCOV version 1.14