LCOV - code coverage report
Current view: top level - lib/design/wrapper - tab_widget.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 37 151 24.5 %
Date: 2024-10-04 11:09:33 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2023 The terCAD team. All rights reserved.
       2             : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
       3             : 
       4             : import 'package:app_finance/_classes/controller/delayed_call.dart';
       5             : import 'package:app_finance/_configs/theme_helper.dart';
       6             : import 'package:app_finance/_ext/build_context_ext.dart';
       7             : import 'package:app_finance/design/wrapper/dots_tab_bar_widget.dart';
       8             : import 'package:app_finance/design/wrapper/text_wrapper.dart';
       9             : import 'package:flutter/material.dart';
      10             : 
      11             : enum TabType {
      12             :   primary,
      13             :   secondary,
      14             :   dots,
      15             : }
      16             : 
      17             : class TabWidget extends StatefulWidget {
      18             :   final List<Tab>? tabs;
      19             :   final List<Widget> children;
      20             :   final double? maxWidth;
      21             :   final int focus;
      22             :   final Function? callback;
      23             :   final TabType type;
      24             :   final bool isLeft;
      25             :   final bool hasIndent;
      26             : 
      27           2 :   const TabWidget({
      28             :     super.key,
      29             :     required this.children,
      30             :     this.maxWidth,
      31             :     this.callback,
      32             :     this.tabs,
      33             :     this.isLeft = false,
      34             :     this.hasIndent = true,
      35             :     this.focus = 0,
      36             :     this.type = TabType.primary,
      37           2 :   }) : assert(tabs != null || type == TabType.dots);
      38             : 
      39           2 :   @override
      40             :   // ignore: no_logic_in_create_state
      41           2 :   BasicTabWidgetState createState() => switch (type) {
      42           2 :         TabType.dots => DotsWidgetState(),
      43           4 :         TabType.secondary => TabSecondaryWidgetState(),
      44           0 :         _ => TabWidgetState(),
      45             :       };
      46             : }
      47             : 
      48             : abstract class BasicTabWidgetState extends State<TabWidget> with TickerProviderStateMixin {
      49             :   late PageController pageController;
      50             :   late TabController tabController;
      51             :   late int tabCount;
      52             :   late int tabIndex;
      53             :   late int initIndex;
      54             :   late bool hasIcons = widget.tabs?.first.icon != null;
      55             :   final timer = DelayedCall(500, preserveFirst: true);
      56             : 
      57             :   PreferredSizeWidget? getAppBar(BuildContext context);
      58             : 
      59             :   Widget buildContent(BuildContext context);
      60             : 
      61           2 :   @override
      62             :   void initState() {
      63           2 :     super.initState();
      64           2 :     initControllers();
      65             :   }
      66             : 
      67           2 :   void initControllers() {
      68           8 :     tabCount = widget.children.length;
      69           6 :     tabIndex = widget.focus;
      70           6 :     initIndex = widget.focus;
      71           6 :     pageController = PageController(initialPage: tabIndex);
      72           4 :     tabController = TabController(
      73           2 :       length: tabCount,
      74             :       vsync: this,
      75           2 :       initialIndex: tabIndex,
      76             :     );
      77             :   }
      78             : 
      79           2 :   @override
      80             :   void dispose() {
      81           4 :     pageController.dispose();
      82           4 :     tabController.dispose();
      83           2 :     super.dispose();
      84             :   }
      85             : 
      86           0 :   void switchTab(int newIndex) {
      87           0 :     if (newIndex < 0 || newIndex >= tabCount || tabIndex == newIndex) {
      88             :       return;
      89             :     }
      90           0 :     setState(() {
      91           0 :       tabController.animateTo(newIndex);
      92           0 :       if (pageController.hasClients) {
      93           0 :         tabIndex += newIndex > tabIndex ? 1 : -1;
      94           0 :         pageController.animateToPage(
      95           0 :           tabIndex,
      96             :           duration: const Duration(milliseconds: 300),
      97             :           curve: Curves.ease,
      98             :         );
      99           0 :         if (tabIndex == newIndex && widget.callback != null) {
     100           0 :           widget.callback!(newIndex);
     101             :         } else {
     102           0 :           timer.run(() {
     103           0 :             switchTab(newIndex);
     104             :           });
     105             :         }
     106           0 :       } else if (widget.callback != null) {
     107           0 :         tabIndex = newIndex;
     108           0 :         widget.callback!(newIndex);
     109             :       }
     110             :     });
     111             :   }
     112             : 
     113           0 :   @override
     114             :   Widget build(BuildContext context) {
     115           0 :     if (tabCount != widget.children.length) {
     116           0 :       WidgetsBinding.instance.addPostFrameCallback((_) => setState(() => initControllers()));
     117             :     }
     118           0 :     if (initIndex != widget.focus) {
     119           0 :       WidgetsBinding.instance.addPostFrameCallback((_) {
     120           0 :         setState(() => initIndex = widget.focus);
     121           0 :         switchTab(widget.focus);
     122             :       });
     123             :     }
     124           0 :     return GestureDetector(
     125           0 :       onHorizontalDragEnd: (DragEndDetails details) {
     126           0 :         if (details.primaryVelocity! > 0) {
     127           0 :           switchTab(tabIndex - 1);
     128           0 :         } else if (details.primaryVelocity! < 0) {
     129           0 :           switchTab(tabIndex + 1);
     130             :         }
     131             :       },
     132           0 :       child: Scaffold(
     133           0 :         appBar: getAppBar(context),
     134           0 :         body: buildContent(context),
     135             :       ),
     136             :     );
     137             :   }
     138             : 
     139           0 :   Widget getLeftAppBar(BuildContext context) {
     140           0 :     final color = context.colorScheme.onInverseSurface;
     141           0 :     final selected = context.textTheme.bodySmall?.copyWith(color: color);
     142           0 :     final style = context.textTheme.bodySmall?.copyWith(color: color.withOpacity(0.6));
     143           0 :     return SizedBox(
     144             :       width: ThemeHelper.barHeight,
     145           0 :       child: NavigationRail(
     146           0 :         selectedIndex: tabIndex,
     147           0 :         onDestinationSelected: switchTab,
     148           0 :         backgroundColor: context.colorScheme.primary,
     149             :         indicatorColor: Colors.transparent,
     150           0 :         labelType: hasIcons ? NavigationRailLabelType.all : NavigationRailLabelType.selected,
     151             :         selectedLabelTextStyle: selected,
     152             :         unselectedLabelTextStyle: style,
     153             :         indicatorShape: InputBorder.none,
     154             :         minWidth: ThemeHelper.barHeight,
     155             :         minExtendedWidth: ThemeHelper.barHeight,
     156             :         groupAlignment: BorderSide.strokeAlignCenter,
     157           0 :         destinations: widget.tabs!
     158           0 :             .asMap()
     159           0 :             .entries
     160           0 :             .map(
     161           0 :               hasIcons
     162           0 :                   ? (e) => NavigationRailDestination(
     163           0 :                         icon: RotatedBox(
     164             :                           quarterTurns: 3,
     165           0 :                           child: TextWrapper(
     166           0 :                             e.value.text ?? '',
     167           0 :                             style: tabIndex == e.key ? selected : style,
     168             :                           ),
     169             :                         ),
     170           0 :                         label: RotatedBox(
     171             :                           quarterTurns: 3,
     172           0 :                           child: e.value.icon != null
     173           0 :                               ? Icon(
     174           0 :                                   (e.value.icon as Icon).icon,
     175           0 :                                   color: tabIndex == e.key ? color : color.withOpacity(0.6),
     176             :                                 )
     177             :                               : null,
     178             :                         ),
     179             :                       )
     180           0 :                   : (e) => NavigationRailDestination(
     181           0 :                         icon: RotatedBox(
     182             :                           quarterTurns: 3,
     183           0 :                           child: Container(
     184             :                             height: 20,
     185           0 :                             decoration: ShapeDecoration(
     186           0 :                               shape: UnderlineInputBorder(
     187           0 :                                 borderSide: BorderSide(
     188           0 :                                   color: color.withOpacity(tabIndex == e.key ? 1 : 0.2),
     189             :                                   width: 2,
     190             :                                 ),
     191             :                               ),
     192             :                             ),
     193           0 :                             child: TextWrapper(e.value.text ?? '', style: style),
     194             :                           ),
     195             :                         ),
     196             :                         label: ThemeHelper.emptyBox,
     197             :                       ),
     198             :             )
     199           0 :             .toList(),
     200             :       ),
     201             :     );
     202             :   }
     203             : 
     204           0 :   Widget buildLeft(BuildContext context) {
     205           0 :     final indent = ThemeHelper.getIndent();
     206           0 :     return Row(
     207           0 :       children: [
     208           0 :         getLeftAppBar(context),
     209           0 :         Expanded(
     210           0 :           child: GestureDetector(
     211           0 :             onHorizontalDragEnd: (DragEndDetails details) {
     212           0 :               if (details.primaryVelocity! > 0) {
     213           0 :                 switchTab(tabIndex - 1);
     214           0 :               } else if (details.primaryVelocity! < 0) {
     215           0 :                 switchTab(tabIndex + 1);
     216             :               }
     217             :             },
     218           0 :             child: Padding(
     219           0 :               padding: widget.hasIndent ? EdgeInsets.fromLTRB(indent, 0, indent, 0) : EdgeInsets.zero,
     220           0 :               child: TabBarView(
     221           0 :                 controller: tabController,
     222           0 :                 children: widget.children,
     223             :               ),
     224             :             ),
     225             :           ),
     226             :         ),
     227             :       ],
     228             :     );
     229             :   }
     230             : }
     231             : 
     232             : class TabWidgetState extends BasicTabWidgetState {
     233           0 :   @override
     234             :   PreferredSizeWidget? getAppBar(BuildContext context) {
     235           0 :     return TabBar(
     236           0 :       controller: tabController,
     237           0 :       onTap: switchTab,
     238           0 :       tabs: widget.tabs!,
     239             :     );
     240             :   }
     241             : 
     242           0 :   @override
     243             :   Widget buildContent(BuildContext context) {
     244           0 :     return PageView(
     245           0 :       controller: pageController,
     246           0 :       onPageChanged: switchTab,
     247           0 :       children: widget.children,
     248             :     );
     249             :   }
     250             : 
     251           0 :   @override
     252             :   Widget build(BuildContext context) {
     253           0 :     if (widget.isLeft) {
     254           0 :       return buildLeft(context);
     255             :     }
     256           0 :     return super.build(context);
     257             :   }
     258             : }
     259             : 
     260             : class DotsWidgetState extends BasicTabWidgetState {
     261           0 :   @override
     262             :   PreferredSizeWidget? getAppBar(BuildContext context) {
     263           0 :     return DotsTabBarWidget(
     264           0 :       tabController: tabController,
     265           0 :       pageController: pageController,
     266           0 :       onTap: switchTab,
     267           0 :       tabList: widget.children,
     268           0 :       indent: ThemeHelper.getIndent(),
     269           0 :       width: widget.maxWidth ?? ThemeHelper.getWidth(context, 2),
     270           0 :       color: context.colorScheme.primary,
     271             :     );
     272             :   }
     273             : 
     274           0 :   @override
     275             :   Widget buildContent(BuildContext context) {
     276           0 :     return Transform.translate(
     277             :       offset: const Offset(0, -20),
     278           0 :       child: PageView(
     279           0 :         controller: pageController,
     280           0 :         onPageChanged: switchTab,
     281           0 :         children: widget.children,
     282             :       ),
     283             :     );
     284             :   }
     285             : }
     286             : 
     287             : class TabSecondaryWidgetState extends BasicTabWidgetState {
     288           2 :   @override
     289             :   PreferredSizeWidget? getAppBar(BuildContext context) {
     290           2 :     return TabBar.secondary(
     291           2 :       controller: tabController,
     292             :       // onTap: switchTab,
     293           4 :       tabs: widget.tabs!,
     294             :     );
     295             :   }
     296             : 
     297           0 :   @override
     298             :   Widget buildContent(BuildContext context) => ThemeHelper.emptyBox;
     299             : 
     300           2 :   @override
     301             :   Widget build(BuildContext context) {
     302           2 :     final indent = ThemeHelper.getIndent();
     303           4 :     if (widget.isLeft) {
     304           0 :       return buildLeft(context);
     305             :     }
     306           2 :     return Column(
     307           2 :       children: [
     308           2 :         getAppBar(context)!,
     309           2 :         Expanded(
     310           2 :           child: Padding(
     311           4 :             padding: widget.hasIndent ? EdgeInsets.fromLTRB(indent, 0, indent, 0) : EdgeInsets.zero,
     312           2 :             child: TabBarView(
     313           2 :               controller: tabController,
     314           4 :               children: widget.children,
     315             :             ),
     316             :           ),
     317             :         ),
     318             :       ],
     319             :     );
     320             :   }
     321             : }

Generated by: LCOV version 1.14