diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9be145f --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..0bb64e4 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 + channel: stable + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..acf5c58 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ + + +## Features + +The floating_tabbar package for Flutter allows you to use the responsive design for all platforms made from the Material Design System. + +## Getting started + +import 'package:floating_tabbar/floating_tabbar.dart + + * This is Floating TabBar widget. + * To use this there must be minimum two items. + * You get TabBar floating and a PageView with it. + * And with [isFloating = false] you'll get normal TabBar PageView + * Support for all device sizes. + +## Usage + +```dart +// HOW TO USE THIS WIDGET + Widget floatingTabBarPageView() { + List tabList() { + List _list = [ + TabItem( + icon: const Icon(Icons.dashboard_outlined, size: 30), + selectedIcon: const Icon(Icons.dashboard, size: 30), + label: "Dashboard", + tabScreen: const Center(child: Text("Dashboard", style: TextStyle(fontSize: 30))), + ), + TabItem( + icon: const Icon(Icons.library_books_outlined, size: 30), + selectedIcon: const Icon(Icons.library_books, size: 30), + label: "Report", + tabScreen: const Center(child: Text("Report", style: TextStyle(fontSize: 30))), + ), + TabItem( + icon: const Icon(Icons.settings_outlined, size: 30), + selectedIcon: const Icon(Icons.settings, size: 30), + label: "Settings", + tabScreen: const Center(child: Text("Settings", style: TextStyle(fontSize: 30))), + ), + ]; + return _list; + } + + AppBar getAppBar() { + return AppBar( + title: const Text("Floating Tabbar Pageview"), + backgroundColor: Colors.transparent, + elevation: 0, + centerTitle: true, + ); + } + + return FloatingTabBarPageView( + tabItemList: tabList(), + title: "FLOAT", + parentAppbar: getAppBar(), + ); + } +``` + +## Additional information + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/lib/Models/tab_item.dart b/lib/Models/tab_item.dart new file mode 100644 index 0000000..40aa51d --- /dev/null +++ b/lib/Models/tab_item.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class TabItem { + final Widget icon; + final Widget selectedIcon; + final String label; + final Widget tabWidget; + const TabItem({ + this.icon = const Icon(Icons.icecream_outlined), + this.selectedIcon = const Icon(Icons.icecream), + required this.label, + required this.tabWidget, + }); +} diff --git a/lib/Services/platform_check.dart b/lib/Services/platform_check.dart new file mode 100644 index 0000000..6b93449 --- /dev/null +++ b/lib/Services/platform_check.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'dart:io'; +import 'package:adaptive_breakpoints/adaptive_breakpoints.dart'; +import 'package:flutter/foundation.dart'; + +class PlatformCheck { + String platformCheck({required BuildContext context}) { + String platform = ''; + + /* Used "adaptive_breakpoints: for defining Web for desktop tablet and mobile */ + bool isDisplayDesktop(BuildContext context) => getWindowType(context) >= AdaptiveWindowType.medium; + bool isDisplaySmallDesktop(BuildContext context) => getWindowType(context) <= AdaptiveWindowType.medium && getWindowType(context) >= AdaptiveWindowType.small; + bool isDisplayMobile(BuildContext context) => getWindowType(context) <= AdaptiveWindowType.xsmall; + + final isDesktop = isDisplayDesktop(context); + final isTablet = isDisplaySmallDesktop(context); + final isMobile = isDisplayMobile(context); + + if (!kIsWeb) { + if (Platform.isAndroid) { + platform = 'Android'; + } else if (Platform.isIOS) { + platform = 'iOS'; + } else if (Platform.isMacOS) { + platform = 'MacOS'; + } else if (Platform.isWindows) { + platform = 'Windows'; + } else if (Platform.isLinux) { + platform = 'Linux'; + } else if (Platform.isFuchsia) { + platform = 'Fuchsia'; + } + } else { + if (isDesktop) { + platform = 'Web Desktop'; + } else if (isTablet) { + platform = 'Web Tablet'; + } else if (isMobile) { + platform = 'Web Mobile'; + } + } + return platform; + } +} diff --git a/lib/Widgets/floater.dart b/lib/Widgets/floater.dart new file mode 100644 index 0000000..4979692 --- /dev/null +++ b/lib/Widgets/floater.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class Floater extends StatelessWidget { + final Widget widget; + const Floater({ + Key? key, + required this.widget, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(10), + child: Material(borderRadius: BorderRadius.circular(10), color: Colors.white, elevation: 10, child: widget), + ); + } +} diff --git a/lib/Widgets/top_tabbar.dart b/lib/Widgets/top_tabbar.dart new file mode 100644 index 0000000..1c43d96 --- /dev/null +++ b/lib/Widgets/top_tabbar.dart @@ -0,0 +1,93 @@ +import 'package:floating_tabbar/Models/tab_item.dart'; +import 'package:flutter/material.dart'; + +class TopTabbar extends StatefulWidget { + final List tabList; + final int initialIndex; + const TopTabbar({ + Key? key, + required this.tabList, + this.initialIndex = 0, + }) : super(key: key); + @override + _TopTabbarState createState() => _TopTabbarState(); +} + +class _TopTabbarState extends State with SingleTickerProviderStateMixin { + List getLabelList() { + List labelList = []; + for (var element in widget.tabList) { + labelList.add(element.label); + } + return labelList; + } + + List getTabWidgetList() { + List tabWidgetList = []; + for (var element in widget.tabList) { + tabWidgetList.add(element.tabWidget); + } + return tabWidgetList; + } + + TabController? controller; + List categories = []; + final List _tabs = []; + + List getTabs(int count) { + _tabs.clear(); + for (String e in categories) { + _tabs.add(getTab(e)); + } + return _tabs; + } + + Tab getTab(String categoryname) { + return Tab(text: categoryname); + } + + @override + void initState() { + categories = getCategoryList(); + controller = TabController(length: categories.length, vsync: this, initialIndex: widget.initialIndex); + getTabs(categories.length); + + super.initState(); + } + + List getCategoryList() { + List list = getLabelList(); + return list; + } + + AppBar customAppbarwithTabbar({TabController? controller, required BuildContext context, required List tabs}) { + return AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + bottom: TabBar( + indicator: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.secondary.withOpacity(0.3), + ), + labelColor: Theme.of(context).primaryColor, + controller: controller, + tabs: tabs, + isScrollable: true, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(50), + child: customAppbarwithTabbar(controller: controller, tabs: _tabs, context: context), + ), + body: TabBarView( + controller: controller, + children: getTabWidgetList(), + ), + ); + } +} diff --git a/lib/floating_tabbar.dart b/lib/floating_tabbar.dart new file mode 100644 index 0000000..d165cba --- /dev/null +++ b/lib/floating_tabbar.dart @@ -0,0 +1,247 @@ +library floating_tabbar; + +import 'package:floating_tabbar/Models/tab_item.dart'; +import 'package:floating_tabbar/Services/platform_check.dart'; +import 'package:floating_tabbar/Widgets/floater.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class FloatingTabBarPageView extends StatefulWidget { + final List tabItemList; + final String title; + final AppBar? parentAppbar; + final bool? isFloating; + final bool? showTabNameForNonFloating; + final bool? showTabNameForFloating; + final Color indicatorColor; + + const FloatingTabBarPageView({ + Key? key, + required this.tabItemList, + required this.title, + this.parentAppbar, + this.indicatorColor = Colors.black12, + this.isFloating = true, + this.showTabNameForNonFloating = false, + this.showTabNameForFloating = false, + }) : super(key: key); + @override + _FloatingTabBarPageViewState createState() => _FloatingTabBarPageViewState(); +} + +class _FloatingTabBarPageViewState extends State { + PageController floatingTabBarPageViewController = PageController(initialPage: 0); + final ValueNotifier playerExpandProgress = ValueNotifier(76); + bool isExtended = false; + bool isFloatingBarHidden = false; + int _selectedIndex = 0; + + List getBottomNavigationBarItemIcon({required bool showTabName}) { + List _bottomNavigationBarItemiconList = []; + for (var element in widget.tabItemList) { + _bottomNavigationBarItemiconList.add( + BottomNavigationBarItem( + icon: element.icon, + activeIcon: element.selectedIcon, + label: showTabName ? element.label : null, + ), + ); + } + return _bottomNavigationBarItemiconList; + } + + List getNavigationRailDestinationList() { + List _list = []; + for (var element in widget.tabItemList) { + _list.add(NavigationRailDestination( + icon: element.icon, + selectedIcon: element.selectedIcon, + label: Text(element.label), + )); + } + return _list; + } + + List getTabScreenList() { + List _tabScreenList = []; + for (var element in widget.tabItemList) { + _tabScreenList.add(element.tabWidget); + } + return _tabScreenList; + } + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + floatingTabBarPageViewController.animateToPage(index, duration: const Duration(milliseconds: 400), curve: Curves.ease); + }); + } + + Widget floatingActionButtonBar() { + Widget floatingBottomBar(value) { + return SizedBox( + height: 60 * (MediaQuery.of(context).size.height - value) / (MediaQuery.of(context).size.height - 50), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 50), + child: Material( + borderRadius: BorderRadius.circular(50), + elevation: 35, + child: ClipRRect( + borderRadius: BorderRadius.circular(50), + child: CupertinoTabBar( + border: const Border( + bottom: BorderSide.none, + left: BorderSide.none, + right: BorderSide.none, + top: BorderSide.none, + ), + currentIndex: _selectedIndex, + iconSize: 35, + items: getBottomNavigationBarItemIcon(showTabName: widget.showTabNameForFloating!), + onTap: (index) { + _onItemTapped(index); + }, + activeColor: Theme.of(context).primaryColor, + ), + ), + ), + ), + ); + } + + return ValueListenableBuilder( + valueListenable: playerExpandProgress, + builder: (BuildContext context, double value, _) { + return floatingBottomBar(value); + }, + ); + } + + CupertinoTabBar nonFloatingBottomBar() { + return CupertinoTabBar( + border: const Border( + bottom: BorderSide.none, + left: BorderSide.none, + right: BorderSide.none, + top: BorderSide.none, + ), + currentIndex: _selectedIndex, + items: getBottomNavigationBarItemIcon(showTabName: widget.showTabNameForNonFloating!), + onTap: (index) { + _onItemTapped(index); + }, + activeColor: Colors.white, + inactiveColor: Colors.white, + backgroundColor: Theme.of(context).primaryColor, + ); + } + + Scaffold buildScafoldForFloatingTabBar() { + return Scaffold( + body: SafeArea( + child: Column( + children: [ + Expanded( + child: PageView( + onPageChanged: (indx) => setState(() => _selectedIndex = indx), + controller: floatingTabBarPageViewController, + children: getTabScreenList(), + ), + ), + ], + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: isFloatingBarHidden ? Container() : floatingActionButtonBar(), + ); + } + + Scaffold buildScafoldForBottomBar() { + return Scaffold( + appBar: widget.parentAppbar, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: PageView( + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (indx) => setState(() => _selectedIndex = indx), + controller: floatingTabBarPageViewController, + children: getTabScreenList(), + ), + ), + ], + ), + ), + bottomNavigationBar: nonFloatingBottomBar(), + ); + } + + Scaffold buildScaffoldForWeb() { + return Scaffold( + body: SafeArea( + child: Row( + children: [ + Floater( + widget: Container( + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + height: MediaQuery.of(context).size.height * 0.99, + child: NavigationRail( + selectedIndex: _selectedIndex, + onDestinationSelected: (int index) { + _onItemTapped(index); + setState(() => _selectedIndex = index); + }, + leading: Row( + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 20), + child: GestureDetector( + onTap: () => setState(() => isExtended = !isExtended), + child: Icon(isExtended ? Icons.arrow_back_ios_new_rounded : Icons.arrow_forward_ios_rounded), + ), + ), + isExtended + ? GestureDetector( + // onTap: () => Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => const /* your App/Web-App Home Widget */), (route) => false), + child: Text(widget.title, style: TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, fontSize: 20)), + ) + : Container(), + ], + ), + extended: isExtended, + useIndicator: true, + indicatorColor: widget.indicatorColor, + minExtendedWidth: MediaQuery.of(context).size.width * 0.15, + labelType: NavigationRailLabelType.none, + destinations: getNavigationRailDestinationList(), + ), + ), + ), + Expanded( + child: PageView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + onPageChanged: (indx) => setState(() => _selectedIndex = indx), + controller: floatingTabBarPageViewController, + children: getTabScreenList(), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + PlatformCheck platFormCheck = PlatformCheck(); + var platform = platFormCheck.platformCheck(context: context); + debugPrint("Platform: $platform"); + + return platform == "Web Desktop" || platform == "Web Tablet" || platform == "Windows" + ? buildScaffoldForWeb() + : (widget.isFloating! ? buildScafoldForFloatingTabBar() : buildScafoldForBottomBar()); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..723b77f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,55 @@ +name: floating_tabbar +description: A new Flutter package project. +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.16.1 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + adaptive_breakpoints: ^0.1.1 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages