├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.md ├── img │ ├── apparence_logo.png │ ├── bart.gif │ ├── bart_new.gif │ └── logo.jpg └── workflows │ └── flutter-ci.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── docs └── img │ └── flutter_template.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── navigation.dart │ ├── routes.dart │ ├── tabs │ │ ├── fake_list.dart │ │ ├── home_page.dart │ │ └── page_counter.dart │ └── widgets │ │ └── simple_bottom_bar.dart ├── pubspec.lock ├── pubspec.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── bart.dart └── bart │ ├── bart_appbar.dart │ ├── bart_bottombar_actions.dart │ ├── bart_model.dart │ ├── bart_scaffold.dart │ ├── router_delegate.dart │ └── widgets │ ├── animated_appbar.dart │ ├── bottom_bar │ ├── bottom_bar.dart │ ├── bottombar_icon.dart │ └── styles │ │ ├── bottom_bar_cupertino.dart │ │ ├── bottom_bar_custom.dart │ │ └── bottom_bar_material.dart │ ├── nested_navigator.dart │ └── side_bar │ ├── custom_sidebar.dart │ ├── rail_sidebar.dart │ └── sidebar.dart ├── pubspec.lock ├── pubspec.yaml └── test ├── bart_test.dart └── components ├── custom_bottom_bar.dart ├── page_counter.dart └── page_fake.dart /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Every request must be reviewed and accepted by: 2 | 3 | * @g-apparence -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Logs ** 24 | Run `flutter analyze` and attach any output of that command below. 25 | If there are any analysis errors, try resolving them before filing this issue. 26 | 27 | Paste the output of running `flutter doctor -v` here. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/img/apparence_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/.github/img/apparence_logo.png -------------------------------------------------------------------------------- /.github/img/bart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/.github/img/bart.gif -------------------------------------------------------------------------------- /.github/img/bart_new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/.github/img/bart_new.gif -------------------------------------------------------------------------------- /.github/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/.github/img/logo.jpg -------------------------------------------------------------------------------- /.github/workflows/flutter-ci.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: subosito/flutter-action@v2 11 | - name: Install Dependencies 12 | run: flutter packages get 13 | - name: Format 14 | run: dart format --set-exit-if-changed lib test example 15 | - name: Analyze 16 | run: flutter analyze lib test example 17 | - name: Run tests 18 | run: flutter test --no-pub --coverage --test-randomize-ordering-seed random 19 | - name: Check Code Coverage 20 | uses: ChicagoFlutter/lcov-cop@v1.0.0 21 | with: 22 | path: packages/alfreed/coverage/lcov.info 23 | - name: Upload coverage to Codecov 24 | uses: codecov/codecov-action@v1 25 | with: 26 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "bart example", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "example/lib/main.dart", 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.4.0] 2 | 3 | - add BartMenuRoute.bottomBarBuilder isActive property to builder 4 | 5 | ## [1.3.0] 6 | 7 | - NavigationRail sidebar or custom sidebar is now available 8 | 9 | ## [1.2.1] 10 | 11 | - Fix prevent rebuilding bottom items when route change 12 | 13 | ## [1.2.0] 14 | 15 | - Add BartMenuRoute.bottomBarBuilder to build a single item bottom bar (so you 16 | can show notification badge on it) 17 | - Add onRouteChanged callback to BartScaffold to get notified when route change 18 | 19 | ## [1.1.0] 20 | 21 | - Material 3 bottom bar theme 22 | - Hide / show bottom bar from action 23 | 24 | ## [1.0.0] 25 | 26 | - enable hot reload 27 | - parent context is now available 28 | - will pop scope is now supported 29 | - nested route can be canceled by tapping on parent tab item 30 | - rework how Bart work to improve stability & performance 31 | 32 | ## [0.3.1] 33 | 34 | - upgrade android example project 35 | 36 | ## [0.3.0] 37 | 38 | - preserve state and scroll within navigation if cache activated 39 | 40 | ## [0.2.0] 41 | 42 | - remove unnecessary null check 43 | 44 | ## [0.1.1] - Transitions 45 | 46 | - handle transition for routes (optionnal) default is none 47 | - handle transition duration for routes (optionnal) default is 300ms 48 | 49 | ## [0.1.0] - Add settings to page build 50 | 51 | - add args setting to sub navigation 52 | 53 | ## [0.0.3] - Cache and Appbar animation 54 | 55 | - show or hide appbar with animation 56 | - preserve page in cache (see readme) 57 | 58 | ## [0.0.1] - First release 59 | 60 | - first release 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2021 Apparence 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, 9 | and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | bart logo 3 |

4 |

5 | 6 |

7 | build 8 | 9 | License: MIT 10 | pub dev bart 11 |

12 | 13 | 14 | ApparenceKit Flutter template to bootstrap your next app 19 | 20 | 21 | [This plugin is also available as a template in ApparenceKit](https://apparencekit.dev).
22 | 23 |
24 |
25 |
26 | 27 | ## 🚀  Overview 28 | 29 | **Bart** is very simple solution to implement tabulation system layout with Navigator into your application. 30 | 31 | - 📱 **Material** & **Cupertino** themes available. 32 | - 🤝 Automatic theme **switching** between material & cupertino. 33 | - 🛣 **Inner** routing inside tab. 34 | - 🥷 **Parent** routing (over tabbar content). 35 | - 😌 **Very easy** to implement. 36 | - 🪄 Show **AppBar** on demand (automatically **animated**). 37 | - 🚀 Create your **own bottom bar design** if you need it. 38 | - 👻 Hide/Show bottom bar when you need 39 | - 🗃 **Cache** route page if you need to **restore state**. 40 | 41 | ## 🧐  Live example 42 | 43 |

44 | Bart example 45 |

46 | 47 | ## 📖  Installation 48 | 49 | ### Install the package 50 | 51 | ```sh 52 | flutter pub add bart 53 | ``` 54 | 55 | ### Import the package 56 | 57 | ```dart 58 | import 'package:bart/bart.dart'; 59 | ``` 60 | 61 | ## 🚀  Get started 62 | 63 | - Define in your page the routing tab 64 | 65 | ```dart 66 | List subRoutes() { 67 | return [ 68 | BartMenuRoute.bottomBar( 69 | label: "Home", 70 | icon: Icons.home, 71 | path: '/home', 72 | pageBuilder: (context) => PageFake( 73 | key: PageStorageKey("home"), // this is required to enable state caching 74 | Colors.red, 75 | ), 76 | ), 77 | BartMenuRoute.bottomBar( 78 | label: "Library", 79 | icon: Icons.video_library_rounded, 80 | path: '/library', 81 | pageBuilder: (context) => PageFake(Colors.blueGrey), 82 | ), 83 | BartMenuRoute.bottomBar( 84 | label: "Profile", 85 | icon: Icons.person, 86 | path: '/profile', 87 | pageBuilder: (context) => PageFake(Colors.yellow), 88 | ), 89 | BartMenuRoute.innerRoute( // add an inner route, no item will be added in bottom bar 90 | path: '/subpage', 91 | pageBuilder: (context) => 92 | PageFake(Colors.greenAccent, child: Text("Sub Route page")), 93 | ), 94 | ]; 95 | } 96 | ``` 97 | 98 |
99 | What are the differences between innerRoute & bottomBar ? 100 |

101 | This creates a route with a bottom menu item: 102 | 103 | ```dart 104 | BartMenuRoute.bottomBar(...) 105 | ``` 106 | 107 | This creates a route that you can push within your scaffold body (no extra item will be added in bottom bar) 108 | 109 | ```dart 110 | BartMenuRoute.innerRoute(...) 111 | ``` 112 | 113 |

114 |
115 | 116 | - Create your main page which include the Bart tabbar Scaffold 117 | 118 | ```dart 119 | class MainPageMenu extends StatelessWidget { 120 | const MainPageMenu({Key? key}) : super(key: key); 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | return BartScaffold( 125 | routesBuilder: subRoutes, // add a reference to the subRoutes list you created before 126 | bottomBar: BartBottomBar.adaptive(), // add the bottom bar (see below for other options) 127 | ); 128 | } 129 | } 130 | ``` 131 | 132 | ## 🏞  Bottom bar themes 133 | 134 | Bart include 4 ways to display a bottom bar: 135 | 136 | ```dart 137 | BartBottomBar.cupertino() // iOS look. 138 | BartBottomBar.material3() // Android look. 139 | BartBottomBar.adaptive() // automatically select between cupertino & material depending on the device. 140 | BartBottomBar.custom() // your own design 141 | ``` 142 | 143 | To custom the bottom bar, simply extends `BartBottomBarCustom` and create your own bottom bar like `SimpleBottomBar`(in example project).
144 | 145 | ## 🤖  Material 3 bottom bar 146 | 147 | If you want to custom your material 3 bottom bar, you can use the `BottomNavigationBarThemeData` class in your theme. 148 | 149 | ```dart 150 | ThemeData( 151 | navigationBarTheme: NavigationBarThemeData( 152 | backgroundColor: Colors.orange.shade100, 153 | indicatorColor: Colors.orange.shade700, 154 | ), 155 | ) 156 | 157 | ``` 158 | 159 | ## 🗃  Bottom icon notfication badge 160 | 161 | You can add a notification badge on your bottom bar icon.
162 | 163 | To do that, you need to use the `BartMenuRoute.bottomBarBuilder` method instead of `BartMenuRoute.bottomBar` in your route definitions: 164 | 165 | ```dart 166 | BartMenuRoute.bottomBarBuilder( 167 | label: "Library", 168 | builder: (context) => BottomBarIcon.builder( 169 | icon: const Icon(Icons.video_library_outlined), 170 | notificationBuilder: (context) => Container( 171 | decoration: const BoxDecoration( 172 | color: Colors.red, 173 | shape: BoxShape.circle, 174 | ), 175 | padding: const EdgeInsets.all(4), 176 | child: const Text( 177 | "1", 178 | style: TextStyle(color: Colors.white, fontSize: 10), 179 | ), 180 | ), 181 | ), 182 | path: '/library', 183 | pageBuilder: (parentContext, tabContext, settings) => const FakeListPage( 184 | key: PageStorageKey("library"), 185 | ), 186 | transitionDuration: bottomBarTransitionDuration, 187 | transitionsBuilder: bottomBarTransition, 188 | ), 189 | ``` 190 | ## 🌏  Web sidebar menu 191 | 192 | You can choose to use the material sidebar [NavigationRail](https://api.flutter.dev/flutter/material/NavigationRail-class.html) 193 | 194 | Like this 195 | ```dart 196 | return BartScaffold( 197 | routesBuilder: subRoutes, 198 | showBottomBarOnStart: true, 199 | bottomBar: BartBottomBar.material3(), 200 | sideBarOptions: RailSideBarOptions( 201 | extended: true, 202 | gravity: Gravity.left, 203 | ), 204 | ) 205 | ``` 206 | (To change the theme you can do it like all regular material theme in the ThemeData). 207 | 208 | Or you can make your own 209 | 210 | ```dart 211 | return BartScaffold( 212 | routesBuilder: subRoutes, 213 | showBottomBarOnStart: true, 214 | bottomBar: BartBottomBar.material3(), 215 | sideBarOptions: CustomSideBarOptions( 216 | gravity: Gravity.left, 217 | sideBarBuilder: ((onTapItem, currentItem) => ...) 218 | ), 219 | ) 220 | ``` 221 | 222 | ## 🗃  State caching 223 | 224 | ### How it works 🤔 ? 225 | 226 | Imagine you got a page with a **counter**. You **increment** this counter and **change tab**. You want this tab to come back with the **incremented counter**?. 227 | 228 | Bart include a **caching system** to implement this feature. 229 | 230 | By **default** state caching is enabled. But you can override it: 231 | 232 | ```dart 233 | BartMenuRoute.bottomBar(cache: false) 234 | ``` 235 | 236 | ### How to use it 🤓 ? 237 | 238 | Your tab pages you want to be cached must use a `PageStorageKey` property: 239 | 240 | ```dart 241 | BartMenuRoute.bottomBar( 242 | label: "Library", 243 | icon: Icons.video_library_rounded, 244 | path: '/library', 245 | pageBuilder: (context, settings) => PageFake( 246 | key: PageStorageKey("library"), // add this property 247 | child: Text('Cached page !'), 248 | ), 249 | ``` 250 | 251 | ## 🗃  Show/Hide animated AppBar 252 | 253 | You can show an animated AppBar or hide it whenever you want inside all your **Bart** sub pages. 254 | 255 | > AppBar will automatically shows or hide with a smooth animation 256 | 257 | Simply add the **AppBarNotifier** mixin like this: 258 | 259 | ```dart 260 | class MyPage extends StatelessWidget with AppBarNotifier { 261 | const MyPage({ Key? key }) : super(key: key); 262 | 263 | @override 264 | Widget build(BuildContext context) { 265 | // use the update app bar method to dynamically change app bar 266 | updateAppBar(context, AppBar(title: Text("test"))); 267 | // now call you can call show method that will start animation 268 | showAppBar(context); 269 | return Container(); 270 | } 271 | } 272 | ``` 273 | 274 | To hide app bar, just execute this code inside your widget with **AppBarNotifier** 275 | 276 | ```dart 277 | hideAppBar(context); 278 | ``` 279 | 280 | > You can also use **Actions** to performs AppBar related actions 281 | 282 | ```dart 283 | Actions.invoke(context, AppBarBuildIntent(AppBar(title: Text("title text")))); 284 | Actions.invoke(context,AppBarAnimationIntent.show()); 285 | Actions.invoke(context,AppBarAnimationIntent.hide()); 286 | ``` 287 | 288 | ## 🫥  Show/hide bottom bar 289 | Sometimes you wants to hide the bottom menu bar. 290 | As this is possible for the appbar you can use a mixin for that. 291 | It just require to be called from a sub context of the BartScaffold. 292 | 293 | ```dart 294 | Actions.invoke(context, BottomBarIntent.show()); 295 | Actions.invoke(context, BottomBarIntent.hide()); 296 | ``` 297 | 298 | Or use the mixin 299 | ```dart 300 | class MyPage extends StatelessWidget with BartNotifier { 301 | const MyPage({ Key? key }) : super(key: key); 302 | 303 | @override 304 | Widget build(BuildContext context) { 305 | // directly show the bottom bar 306 | showBottomBar(context); 307 | // directly hide the bottom bar 308 | hideBottomBar(context); 309 | return Container(); 310 | } 311 | } 312 | ``` 313 | 314 | 315 | ## 💫  Transitions between items 316 | 317 | You can use the official [**animation plugin**](https://pub.dev/packages/animations) to create better transition or create your owns. 318 | 319 | Example: 320 | 321 | ```dart 322 | BartMenuRoute.bottomBar( 323 | label: "Library", 324 | icon: Icons.video_library_rounded, 325 | path: '/library', 326 | pageBuilder: (context, settings) => PageFake(Colors.blueGrey), 327 | transitionDuration: Duration(milliseconds: 500), 328 | transitionsBuilder: (context, anim1, anim2, widget) => FadeThroughTransition( 329 | animation: anim1, 330 | secondaryAnimation: anim2, 331 | child: widget, 332 | ), 333 | ), 334 | ``` 335 | 336 | ## 📣  Sponsor 337 | 338 | logo apparence io 339 |
340 |
341 | 342 | [Initiated and sponsored by Apparence.io.](https://apparence.io) 343 | 344 | ## 👥  Contribution 345 | 346 | Contributions are welcome. 347 | Contribute by creating a PR or create an issue 🎉. 348 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /docs/img/flutter_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/docs/img/flutter_template.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 676cefaaff197f27424942307668886253e1ec35 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 676cefaaff197f27424942307668886253e1ec35 17 | base_revision: 676cefaaff197f27424942307668886253e1ec35 18 | - platform: ios 19 | create_revision: 676cefaaff197f27424942307668886253e1ec35 20 | base_revision: 676cefaaff197f27424942307668886253e1ec35 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1430; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | alwaysOutOfDate = 1; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | ); 178 | inputPaths = ( 179 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 180 | ); 181 | name = "Thin Binary"; 182 | outputPaths = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | shellPath = /bin/sh; 186 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 187 | }; 188 | 9740EEB61CF901F6004384FC /* Run Script */ = { 189 | isa = PBXShellScriptBuildPhase; 190 | alwaysOutOfDate = 1; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | inputPaths = ( 195 | ); 196 | name = "Run Script"; 197 | outputPaths = ( 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | shellPath = /bin/sh; 201 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 202 | }; 203 | /* End PBXShellScriptBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 97C146EA1CF9000F007C117D /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 211 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 97C146FB1CF9000F007C117D /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 97C147001CF9000F007C117D /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 279 | MTL_ENABLE_DEBUG_INFO = NO; 280 | SDKROOT = iphoneos; 281 | SUPPORTED_PLATFORMS = iphoneos; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Profile; 286 | }; 287 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 288 | isa = XCBuildConfiguration; 289 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | CLANG_ENABLE_MODULES = YES; 293 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 294 | DEVELOPMENT_TEAM = C6D6DBHW5C; 295 | ENABLE_BITCODE = NO; 296 | INFOPLIST_FILE = Runner/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = ( 298 | "$(inherited)", 299 | "@executable_path/Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 304 | SWIFT_VERSION = 5.0; 305 | VERSIONING_SYSTEM = "apple-generic"; 306 | }; 307 | name = Profile; 308 | }; 309 | 97C147031CF9000F007C117D /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_COMMA = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 333 | CLANG_WARN_STRICT_PROTOTYPES = YES; 334 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = dwarf; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | ENABLE_TESTABILITY = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 357 | MTL_ENABLE_DEBUG_INFO = YES; 358 | ONLY_ACTIVE_ARCH = YES; 359 | SDKROOT = iphoneos; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | }; 362 | name = Debug; 363 | }; 364 | 97C147041CF9000F007C117D /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_COMMA = YES; 376 | CLANG_WARN_CONSTANT_CONVERSION = YES; 377 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 388 | CLANG_WARN_STRICT_PROTOTYPES = YES; 389 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 390 | CLANG_WARN_UNREACHABLE_CODE = YES; 391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 392 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 393 | COPY_PHASE_STRIP = NO; 394 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 395 | ENABLE_NS_ASSERTIONS = NO; 396 | ENABLE_STRICT_OBJC_MSGSEND = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 406 | MTL_ENABLE_DEBUG_INFO = NO; 407 | SDKROOT = iphoneos; 408 | SUPPORTED_PLATFORMS = iphoneos; 409 | SWIFT_COMPILATION_MODE = wholemodule; 410 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | VALIDATE_PRODUCT = YES; 413 | }; 414 | name = Release; 415 | }; 416 | 97C147061CF9000F007C117D /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 419 | buildSettings = { 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | CLANG_ENABLE_MODULES = YES; 422 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 423 | DEVELOPMENT_TEAM = C6D6DBHW5C; 424 | ENABLE_BITCODE = NO; 425 | INFOPLIST_FILE = Runner/Info.plist; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | ); 430 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 431 | PRODUCT_NAME = "$(TARGET_NAME)"; 432 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 434 | SWIFT_VERSION = 5.0; 435 | VERSIONING_SYSTEM = "apple-generic"; 436 | }; 437 | name = Debug; 438 | }; 439 | 97C147071CF9000F007C117D /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | DEVELOPMENT_TEAM = C6D6DBHW5C; 447 | ENABLE_BITCODE = NO; 448 | INFOPLIST_FILE = Runner/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "@executable_path/Frameworks", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 456 | SWIFT_VERSION = 5.0; 457 | VERSIONING_SYSTEM = "apple-generic"; 458 | }; 459 | name = Release; 460 | }; 461 | /* End XCBuildConfiguration section */ 462 | 463 | /* Begin XCConfigurationList section */ 464 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 465 | isa = XCConfigurationList; 466 | buildConfigurations = ( 467 | 97C147031CF9000F007C117D /* Debug */, 468 | 97C147041CF9000F007C117D /* Release */, 469 | 249021D3217E4FDB00AE95B9 /* Profile */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | 97C147061CF9000F007C117D /* Debug */, 478 | 97C147071CF9000F007C117D /* Release */, 479 | 249021D4217E4FDB00AE95B9 /* Profile */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 487 | } 488 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'navigation.dart'; 4 | 5 | void main() { 6 | runApp(const MyApp()); 7 | } 8 | 9 | class MyApp extends StatelessWidget { 10 | const MyApp({Key? key}) : super(key: key); 11 | 12 | // This widget is the root of your application. 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: 'Flutter Demo', 17 | onGenerateRoute: routes, 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | visualDensity: VisualDensity.adaptivePlatformDensity, 21 | ), 22 | ); 23 | } 24 | } 25 | 26 | Route routes(RouteSettings settings) { 27 | switch (settings.name) { 28 | case '/': 29 | return MaterialPageRoute( 30 | builder: (_) => const MainPageMenu(), 31 | ); 32 | case '/parent': 33 | return MaterialPageRoute( 34 | builder: (_) => Scaffold( 35 | appBar: AppBar(), 36 | body: const Center( 37 | child: Text('Parent'), 38 | ), 39 | ), 40 | ); 41 | default: 42 | throw 'unexpected Route'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart.dart'; 2 | import 'package:example/routes.dart'; 3 | import 'package:example/widgets/simple_bottom_bar.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class MainPageMenu extends StatelessWidget { 7 | const MainPageMenu({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return LayoutBuilder(builder: (context, constraints) { 12 | final width = constraints.maxWidth; 13 | 14 | /// This is an example on how you can handle sidebar for different screen sizes 15 | if (width > 750) { 16 | /// this will use the NavigationRail sidebar from Material 17 | /// If you want to build your own sidebar, you can use the [CustomSideBarOptions] 18 | return BartScaffold( 19 | routesBuilder: subRoutes, 20 | initialRoute: '/library', 21 | showBottomBarOnStart: true, 22 | bottomBar: BartBottomBar.material3(), 23 | sideBarOptions: RailSideBarOptions( 24 | extended: width > 1250, 25 | ), 26 | ); 27 | } else { 28 | /// this will create a navigation bar at the bottom of the screen 29 | /// Using cupertino or material depending on the platform 30 | return BartScaffold( 31 | routesBuilder: subRoutes, 32 | showBottomBarOnStart: true, 33 | // bottomBar: BartBottomBar.adaptive(), 34 | // uncomment to use an exemple of custom bottom bar 35 | bottomBar: BartBottomBar.custom( 36 | bottomBarFactory: SimpleBottomBar(), 37 | ), 38 | ); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/lib/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:animations/animations.dart'; 2 | import 'package:bart/bart.dart'; 3 | import 'package:example/tabs/fake_list.dart'; 4 | import 'package:example/tabs/page_counter.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'tabs/home_page.dart'; 8 | 9 | final GlobalKey navigatorKey = GlobalKey(); 10 | 11 | Future appPushNamed(String route, {Object? arguments}) => 12 | navigatorKey.currentState!.pushNamed(route, arguments: arguments); 13 | 14 | List subRoutes() { 15 | return [ 16 | BartMenuRoute.bottomBar( 17 | label: "Home", 18 | icon: Icons.home_outlined, 19 | selectedIcon: Icons.home, 20 | path: '/home', 21 | pageBuilder: (parentContext, tabContext, settings) => HomePage( 22 | key: const PageStorageKey("home"), 23 | parentContext: parentContext, 24 | ), 25 | transitionDuration: bottomBarTransitionDuration, 26 | transitionsBuilder: bottomBarTransition, 27 | cache: false, 28 | ), 29 | BartMenuRoute.bottomBarBuilder( 30 | label: "Library", 31 | builder: (context, isActive) { 32 | return BottomBarIcon.builder( 33 | icon: Icon( 34 | Icons.notifications, 35 | color: isActive ? Colors.blue : Colors.grey, 36 | ), 37 | top: -4.0, 38 | right: 0.0, 39 | notificationBuilder: (context) => Container( 40 | decoration: const BoxDecoration( 41 | color: Colors.red, 42 | shape: BoxShape.circle, 43 | ), 44 | padding: const EdgeInsets.all(4), 45 | child: const Text( 46 | "1", 47 | style: TextStyle(color: Colors.white, fontSize: 10), 48 | ), 49 | ), 50 | ); 51 | }, 52 | path: '/library', 53 | pageBuilder: (parentContext, tabContext, settings) => const FakeListPage( 54 | key: PageStorageKey("library"), 55 | ), 56 | transitionDuration: bottomBarTransitionDuration, 57 | transitionsBuilder: bottomBarTransition, 58 | ), 59 | BartMenuRoute.bottomBar( 60 | label: "Profile", 61 | icon: Icons.person_outline, 62 | selectedIcon: Icons.person, 63 | path: '/profile', 64 | pageBuilder: (parentContext, tabContext, settings) => Container( 65 | key: const PageStorageKey("profile"), 66 | child: const Center(child: Text('Profile page'))), 67 | transitionDuration: bottomBarTransitionDuration, 68 | transitionsBuilder: bottomBarTransition, 69 | ), 70 | BartMenuRoute.bottomBar( 71 | label: "Counter", 72 | icon: Icons.countertops, 73 | path: '/counter', 74 | pageBuilder: (parentContext, tabContext, settings) => PageFakeCounter( 75 | key: const PageStorageKey("counter"), 76 | showAppBar: true, 77 | ), 78 | transitionDuration: bottomBarTransitionDuration, 79 | transitionsBuilder: bottomBarTransition, 80 | ), 81 | BartMenuRoute.innerRoute( 82 | path: '/home/inner', 83 | pageBuilder: (parentContext, tabContext, settings) => 84 | const Center(child: Text("Sub Route page")), 85 | ), 86 | ]; 87 | } 88 | 89 | Widget bottomBarTransition( 90 | BuildContext c, 91 | Animation a1, 92 | Animation a2, 93 | Widget child, 94 | ) => 95 | FadeThroughTransition( 96 | animation: a1, 97 | secondaryAnimation: a2, 98 | fillColor: Colors.white, 99 | child: child, 100 | ); 101 | 102 | const bottomBarTransitionDuration = Duration(milliseconds: 300); 103 | -------------------------------------------------------------------------------- /example/lib/tabs/fake_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FakeListPage extends StatelessWidget { 4 | const FakeListPage({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | color: Colors.white, 10 | child: ListView.builder( 11 | itemExtent: 250.0, 12 | itemBuilder: (BuildContext context, int index) => Container( 13 | padding: const EdgeInsets.all(10.0), 14 | child: Material( 15 | color: index.isEven ? Colors.cyan : Colors.deepOrange, 16 | child: Center( 17 | child: Text(index.toString()), 18 | ), 19 | ), 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/tabs/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_bottombar_actions.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:bart/bart.dart'; 5 | 6 | class HomePage extends StatelessWidget with AppBarNotifier, BartNotifier { 7 | final BuildContext parentContext; 8 | const HomePage({ 9 | Key? key, 10 | required this.parentContext, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Center( 16 | child: SingleChildScrollView( 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | TextButton( 21 | child: const Text( 22 | "Add AppBar", 23 | style: TextStyle(color: Colors.black), 24 | ), 25 | onPressed: () { 26 | updateAppBar( 27 | context, 28 | AppBar( 29 | title: const Text("title text"), 30 | ), 31 | ); 32 | showAppBar(context); 33 | }, 34 | ), 35 | TextButton( 36 | child: const Text( 37 | "Hide AppBar", 38 | ), 39 | onPressed: () => showBottomBar(context), 40 | ), 41 | TextButton( 42 | child: const Text( 43 | "Show BottomBar", 44 | ), 45 | onPressed: () => showBottomBar(context), 46 | ), 47 | TextButton( 48 | child: const Text( 49 | "Hide BottomBar", 50 | ), 51 | onPressed: () => hideBottomBar(context), 52 | ), 53 | const Divider(), 54 | TextButton( 55 | child: const Text( 56 | "Open page over tabbar", 57 | ), 58 | onPressed: () => Navigator.of(parentContext).pushNamed("/parent"), 59 | ), 60 | const Divider(), 61 | TextButton( 62 | key: const ValueKey("subpageBtn"), 63 | child: const Text( 64 | "Go to inner page", 65 | ), 66 | onPressed: () => Navigator.of(context).pushNamed("/home/inner"), 67 | ), 68 | const Divider(), 69 | TextButton( 70 | child: const Text( 71 | "Go to library tab", 72 | ), 73 | onPressed: () => Navigator.of(context).pushNamed("/library"), 74 | ), 75 | TextButton( 76 | child: const Text( 77 | "Go to profile tab", 78 | ), 79 | onPressed: () => Navigator.of(context).pushNamed("/profile"), 80 | ), 81 | TextButton( 82 | child: const Text( 83 | "Go to counter tab", 84 | ), 85 | onPressed: () => Navigator.of(context).pushNamed("/counter"), 86 | ), 87 | ], 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/tabs/page_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PageFakeCounter extends StatefulWidget { 5 | final bool showAppBar; 6 | final ValueNotifier counterNotifier = ValueNotifier(0); 7 | 8 | PageFakeCounter({Key? key, this.showAppBar = false}) : super(key: key); 9 | 10 | @override 11 | PageFakeCounterState createState() => PageFakeCounterState(); 12 | } 13 | 14 | @visibleForTesting 15 | class PageFakeCounterState extends State with AppBarNotifier { 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | if (widget.showAppBar) { 21 | updateAppBar(context, AppBar(title: const Text("Counter Tab Page"))); 22 | showAppBar(context); 23 | } 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return ValueListenableBuilder( 29 | valueListenable: widget.counterNotifier, 30 | builder: (context, counter, child) => Container( 31 | color: Colors.white, 32 | child: Column( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | children: [ 35 | Text( 36 | "$counter", 37 | style: const TextStyle(fontSize: 30.0), 38 | key: const ValueKey("counter"), 39 | ), 40 | const SizedBox( 41 | height: 10, 42 | ), 43 | TextButton( 44 | key: const ValueKey("addCountBtn"), 45 | onPressed: () { 46 | widget.counterNotifier.value++; 47 | }, 48 | child: const Text("Increment"), 49 | ) 50 | ], 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/lib/widgets/simple_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart.dart'; 2 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_custom.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class SimpleBottomBar extends BartBottomBarFactory { 6 | @override 7 | Widget create({ 8 | required BuildContext context, 9 | required List routes, 10 | required void Function(int) onTap, 11 | required int currentIndex, 12 | }) { 13 | return Container( 14 | height: 85, 15 | decoration: BoxDecoration( 16 | color: Colors.blue, 17 | boxShadow: [ 18 | BoxShadow( 19 | color: Colors.black.withOpacity(0.7), 20 | blurRadius: 3, 21 | ), 22 | ], 23 | ), 24 | child: SafeArea( 25 | child: Row( 26 | children: [ 27 | for (var i = 0; i < routes.length; i++) 28 | Expanded( 29 | child: GestureDetector( 30 | onTap: () => onTap(i), 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | Icon( 35 | routes[i].icon, 36 | color: currentIndex == i ? Colors.white : Colors.black, 37 | ), 38 | const SizedBox(height: 4), 39 | Text( 40 | routes[i].label!, 41 | style: TextStyle( 42 | color: 43 | currentIndex == i ? Colors.white : Colors.black, 44 | ), 45 | ), 46 | ], 47 | ), 48 | ), 49 | ), 50 | ], 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animations: 5 | dependency: "direct main" 6 | description: 7 | name: animations 8 | sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.0.11" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | bart: 21 | dependency: "direct main" 22 | description: 23 | path: ".." 24 | relative: true 25 | source: path 26 | version: "1.4.1" 27 | boolean_selector: 28 | dependency: transitive 29 | description: 30 | name: boolean_selector 31 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 32 | url: "https://pub.dev" 33 | source: hosted 34 | version: "2.1.1" 35 | characters: 36 | dependency: transitive 37 | description: 38 | name: characters 39 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 40 | url: "https://pub.dev" 41 | source: hosted 42 | version: "1.3.0" 43 | charcode: 44 | dependency: transitive 45 | description: 46 | name: charcode 47 | sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 48 | url: "https://pub.dev" 49 | source: hosted 50 | version: "1.3.1" 51 | clock: 52 | dependency: transitive 53 | description: 54 | name: clock 55 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 56 | url: "https://pub.dev" 57 | source: hosted 58 | version: "1.1.1" 59 | collection: 60 | dependency: transitive 61 | description: 62 | name: collection 63 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 64 | url: "https://pub.dev" 65 | source: hosted 66 | version: "1.18.0" 67 | csslib: 68 | dependency: transitive 69 | description: 70 | name: csslib 71 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 72 | url: "https://pub.dev" 73 | source: hosted 74 | version: "1.0.0" 75 | cupertino_icons: 76 | dependency: "direct main" 77 | description: 78 | name: cupertino_icons 79 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 80 | url: "https://pub.dev" 81 | source: hosted 82 | version: "1.0.6" 83 | fake_async: 84 | dependency: transitive 85 | description: 86 | name: fake_async 87 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 88 | url: "https://pub.dev" 89 | source: hosted 90 | version: "1.3.1" 91 | flutter: 92 | dependency: "direct main" 93 | description: flutter 94 | source: sdk 95 | version: "0.0.0" 96 | flutter_lints: 97 | dependency: "direct dev" 98 | description: 99 | name: flutter_lints 100 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 101 | url: "https://pub.dev" 102 | source: hosted 103 | version: "2.0.3" 104 | flutter_test: 105 | dependency: "direct dev" 106 | description: flutter 107 | source: sdk 108 | version: "0.0.0" 109 | html: 110 | dependency: transitive 111 | description: 112 | name: html 113 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 114 | url: "https://pub.dev" 115 | source: hosted 116 | version: "0.15.4" 117 | lints: 118 | dependency: transitive 119 | description: 120 | name: lints 121 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 122 | url: "https://pub.dev" 123 | source: hosted 124 | version: "2.1.1" 125 | matcher: 126 | dependency: transitive 127 | description: 128 | name: matcher 129 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 130 | url: "https://pub.dev" 131 | source: hosted 132 | version: "0.12.16" 133 | material_color_utilities: 134 | dependency: transitive 135 | description: 136 | name: material_color_utilities 137 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 138 | url: "https://pub.dev" 139 | source: hosted 140 | version: "0.5.0" 141 | meta: 142 | dependency: transitive 143 | description: 144 | name: meta 145 | sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e 146 | url: "https://pub.dev" 147 | source: hosted 148 | version: "1.10.0" 149 | path: 150 | dependency: transitive 151 | description: 152 | name: path 153 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 154 | url: "https://pub.dev" 155 | source: hosted 156 | version: "1.8.3" 157 | sky_engine: 158 | dependency: transitive 159 | description: flutter 160 | source: sdk 161 | version: "0.0.99" 162 | source_span: 163 | dependency: transitive 164 | description: 165 | name: source_span 166 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "1.10.0" 170 | stack_trace: 171 | dependency: transitive 172 | description: 173 | name: stack_trace 174 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 175 | url: "https://pub.dev" 176 | source: hosted 177 | version: "1.11.1" 178 | stream_channel: 179 | dependency: transitive 180 | description: 181 | name: stream_channel 182 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 183 | url: "https://pub.dev" 184 | source: hosted 185 | version: "2.1.2" 186 | string_scanner: 187 | dependency: transitive 188 | description: 189 | name: string_scanner 190 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 191 | url: "https://pub.dev" 192 | source: hosted 193 | version: "1.2.0" 194 | term_glyph: 195 | dependency: transitive 196 | description: 197 | name: term_glyph 198 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "1.2.1" 202 | test_api: 203 | dependency: transitive 204 | description: 205 | name: test_api 206 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "0.6.1" 210 | typed_data: 211 | dependency: transitive 212 | description: 213 | name: typed_data 214 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "1.3.2" 218 | universal_html: 219 | dependency: transitive 220 | description: 221 | name: universal_html 222 | sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" 223 | url: "https://pub.dev" 224 | source: hosted 225 | version: "2.2.4" 226 | universal_io: 227 | dependency: transitive 228 | description: 229 | name: universal_io 230 | sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" 231 | url: "https://pub.dev" 232 | source: hosted 233 | version: "2.2.2" 234 | vector_math: 235 | dependency: transitive 236 | description: 237 | name: vector_math 238 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 239 | url: "https://pub.dev" 240 | source: hosted 241 | version: "2.1.4" 242 | web: 243 | dependency: transitive 244 | description: 245 | name: web 246 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 247 | url: "https://pub.dev" 248 | source: hosted 249 | version: "0.3.0" 250 | sdks: 251 | dart: ">=3.2.0 <4.0.0" 252 | flutter: ">=3.16.0" 253 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.0 <3.0.0" 22 | 23 | dependencies: 24 | animations: ^2.0.4 25 | flutter: 26 | sdk: flutter 27 | bart: 28 | path: ../ 29 | 30 | 31 | # The following adds the Cupertino Icons font to your application. 32 | # Use with the CupertinoIcons class for iOS style icons. 33 | cupertino_icons: ^1.0.5 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | flutter_lints: ^2.0.1 39 | 40 | # For information on the generic Dart part of this file, see the 41 | # following page: https://dart.dev/tools/pub/pubspec 42 | 43 | # The following section is specific to Flutter. 44 | flutter: 45 | 46 | # The following line ensures that the Material Icons font is 47 | # included with your application, so that you can use the icons in 48 | # the material Icons class. 49 | uses-material-design: true 50 | 51 | # To add assets to your application, add an assets section, like this: 52 | # assets: 53 | # - images/a_dot_burr.jpeg 54 | # - images/a_dot_ham.jpeg 55 | 56 | # An image asset can refer to one or more resolution-specific "variants", see 57 | # https://flutter.dev/assets-and-images/#resolution-aware. 58 | 59 | # For details regarding adding assets from package dependencies, see 60 | # https://flutter.dev/assets-and-images/#from-packages 61 | 62 | # To add custom fonts to your application, add a fonts section here, 63 | # in this "flutter" section. Each entry in this list should have a 64 | # "family" key with the font family name, and a "fonts" key with a 65 | # list giving the asset and other descriptors for the font. For 66 | # example: 67 | # fonts: 68 | # - family: Schyler 69 | # fonts: 70 | # - asset: fonts/Schyler-Regular.ttf 71 | # - asset: fonts/Schyler-Italic.ttf 72 | # style: italic 73 | # - family: Trajan Pro 74 | # fonts: 75 | # - asset: fonts/TrajanPro.ttf 76 | # - asset: fonts/TrajanPro_Bold.ttf 77 | # weight: 700 78 | # 79 | # For details regarding fonts from package dependencies, 80 | # see https://flutter.dev/custom-fonts/#from-packages 81 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/bart/cbfe3c061717037658afae2425d730f07e1d7c5b/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/bart.dart: -------------------------------------------------------------------------------- 1 | export './bart/bart_appbar.dart'; 2 | export './bart/bart_model.dart'; 3 | export './bart/bart_scaffold.dart'; 4 | export './bart/widgets/bottom_bar/bottom_bar.dart'; 5 | export './bart/widgets/animated_appbar.dart'; 6 | export './bart/widgets/bottom_bar/bottombar_icon.dart'; 7 | export './bart/widgets/side_bar/sidebar.dart'; 8 | -------------------------------------------------------------------------------- /lib/bart/bart_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'widgets/animated_appbar.dart'; 4 | 5 | /// Use this intent to provide a custom appBar within a child widget to the scaffold 6 | class AppBarBuildIntent extends Intent { 7 | final PreferredSizeWidget? appbar; 8 | 9 | const AppBarBuildIntent(this.appbar); 10 | 11 | factory AppBarBuildIntent.empty() => const AppBarBuildIntent(null); 12 | } 13 | 14 | /// you can change app bar within your page by calling once 15 | /// Actions.invoke(context, AppBarBuildIntent(AppBar(title: Text("title text")))); 16 | class BartAppBarAction extends Action { 17 | ValueNotifier appbar; 18 | 19 | BartAppBarAction(this.appbar); 20 | 21 | @override 22 | void invoke(covariant AppBarBuildIntent intent) { 23 | appbar.value = intent.appbar; 24 | } 25 | } 26 | 27 | mixin AppBarNotifier { 28 | void updateAppBar(BuildContext context, PreferredSizeWidget? appBar) { 29 | _runWhenReady( 30 | context, 31 | () => Actions.invoke(context, AppBarBuildIntent(appBar)), 32 | ); 33 | } 34 | 35 | void showAppBar(BuildContext context) { 36 | _runWhenReady( 37 | context, 38 | () => Actions.invoke(context, AppBarAnimationIntent.show()), 39 | ); 40 | } 41 | 42 | void hideAppBar(BuildContext context) { 43 | _runWhenReady( 44 | context, 45 | () => Actions.invoke(context, AppBarAnimationIntent.hide()), 46 | ); 47 | } 48 | 49 | _runWhenReady(BuildContext context, Function onReady) { 50 | WidgetsBinding.instance.addPostFrameCallback((_) { 51 | onReady(); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/bart/bart_bottombar_actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class BottomBarIntent extends Intent { 5 | final bool state; 6 | 7 | const BottomBarIntent(this.state); 8 | 9 | factory BottomBarIntent.show() => const BottomBarIntent(true); 10 | 11 | factory BottomBarIntent.hide() => const BottomBarIntent(false); 12 | } 13 | 14 | class BottomBarAction extends Action { 15 | ValueNotifier show; 16 | 17 | BottomBarAction(this.show); 18 | 19 | @override 20 | void invoke(covariant BottomBarIntent intent) { 21 | show.value = intent.state; 22 | } 23 | } 24 | 25 | class AnimatedBottomBar extends StatelessWidget { 26 | final ValueNotifier showStateNotifier; 27 | final BartBottomBar bottomBar; 28 | 29 | const AnimatedBottomBar({ 30 | super.key, 31 | required this.showStateNotifier, 32 | required this.bottomBar, 33 | }); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return ValueListenableBuilder( 38 | valueListenable: showStateNotifier, 39 | child: bottomBar, 40 | builder: (context, show, child) { 41 | if (!show) { 42 | return const SizedBox(height: 0, width: 0); 43 | } 44 | return child!; 45 | }, 46 | ); 47 | } 48 | } 49 | 50 | mixin BartNotifier { 51 | void showBottomBar(BuildContext context) { 52 | _runWhenReady( 53 | context, 54 | () => Actions.invoke(context, BottomBarIntent.show()), 55 | ); 56 | } 57 | 58 | void hideBottomBar(BuildContext context) { 59 | _runWhenReady( 60 | context, 61 | () => Actions.invoke(context, BottomBarIntent.hide()), 62 | ); 63 | } 64 | 65 | _runWhenReady(BuildContext context, Function onReady) { 66 | WidgetsBinding.instance.addPostFrameCallback((_) { 67 | onReady(); 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/bart/bart_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | abstract class MenuRoutePath {} 5 | 6 | enum BartMenuRouteType { bottomNavigation, subRoute } 7 | 8 | typedef BartPageBuilder = Widget Function( 9 | BuildContext parentContext, 10 | BuildContext tabContext, 11 | RouteSettings? settings, 12 | ); 13 | 14 | typedef IconBuilder = Widget Function( 15 | BuildContext context, 16 | bool isActive, 17 | ); 18 | 19 | class BartMenuRoute { 20 | String? label; 21 | IconData? icon; 22 | IconBuilder? iconBuilder; 23 | 24 | /// The optional [IconData] that's displayed when this 25 | /// [NavigationDestination] is selected. 26 | /// Only used for material 3 style bottom bar. 27 | IconData? selectedIcon; 28 | String path; 29 | BartPageBuilder pageBuilder; 30 | RouteSettings settings; 31 | bool? maintainState; 32 | bool cache; 33 | bool showBottomBar; 34 | BartMenuRouteType type; 35 | final RouteTransitionsBuilder? transitionsBuilder; 36 | final Duration? transitionDuration; 37 | 38 | BartMenuRoute._({ 39 | this.label, 40 | this.icon, 41 | this.iconBuilder, 42 | this.selectedIcon, 43 | required this.path, 44 | required this.pageBuilder, 45 | required this.settings, 46 | required this.type, 47 | required this.cache, 48 | required this.showBottomBar, 49 | // ignore: unused_element 50 | this.maintainState, 51 | this.transitionsBuilder, 52 | this.transitionDuration, 53 | }) { 54 | // assert( 55 | // icon != null || iconBuilder != null, 56 | // "You must provide an icon or an iconWidget", 57 | // ); 58 | assert( 59 | icon == null || iconBuilder == null, 60 | "You can't provide both an icon and an iconWidget", 61 | ); 62 | } 63 | 64 | factory BartMenuRoute.bottomBar({ 65 | required String label, 66 | required IconData icon, 67 | required String path, 68 | required BartPageBuilder pageBuilder, 69 | RouteTransitionsBuilder? transitionsBuilder, 70 | Duration? transitionDuration, 71 | bool cache = true, 72 | IconData? selectedIcon, 73 | }) => 74 | BartMenuRoute._( 75 | label: label, 76 | icon: icon, 77 | path: path, 78 | cache: cache, 79 | type: BartMenuRouteType.bottomNavigation, 80 | pageBuilder: pageBuilder, 81 | settings: RouteSettings(name: path), 82 | transitionsBuilder: transitionsBuilder, 83 | transitionDuration: transitionDuration, 84 | selectedIcon: selectedIcon, 85 | showBottomBar: true, 86 | ); 87 | 88 | factory BartMenuRoute.bottomBarBuilder({ 89 | required String? label, 90 | required IconBuilder builder, 91 | required String path, 92 | required BartPageBuilder pageBuilder, 93 | RouteTransitionsBuilder? transitionsBuilder, 94 | Duration? transitionDuration, 95 | bool cache = true, 96 | IconData? selectedIcon, 97 | }) => 98 | BartMenuRoute._( 99 | label: label, 100 | iconBuilder: builder, 101 | path: path, 102 | cache: cache, 103 | type: BartMenuRouteType.bottomNavigation, 104 | pageBuilder: pageBuilder, 105 | settings: RouteSettings(name: path), 106 | transitionsBuilder: transitionsBuilder, 107 | transitionDuration: transitionDuration, 108 | selectedIcon: selectedIcon, 109 | showBottomBar: true, 110 | ); 111 | 112 | factory BartMenuRoute.innerRoute({ 113 | required String path, 114 | required BartPageBuilder pageBuilder, 115 | RouteTransitionsBuilder? transitionsBuilder, 116 | Duration? transitionDuration, 117 | bool cache = false, 118 | bool showBottomBar = true, 119 | }) => 120 | BartMenuRoute._( 121 | path: path, 122 | type: BartMenuRouteType.subRoute, 123 | pageBuilder: pageBuilder, 124 | cache: cache, 125 | settings: RouteSettings(name: path), 126 | transitionsBuilder: transitionsBuilder, 127 | transitionDuration: transitionDuration, 128 | showBottomBar: showBottomBar, 129 | ); 130 | } 131 | 132 | class ScaffoldOptions { 133 | final Key? key; 134 | final Widget? floatingActionButton; 135 | final FloatingActionButtonLocation? floatingActionButtonLocation; 136 | final FloatingActionButtonAnimator? floatingActionButtonAnimator; 137 | final List? persistentFooterButtons; 138 | final Widget? drawer; 139 | final DrawerCallback? onDrawerChanged; 140 | final Widget? endDrawer; 141 | final DrawerCallback? onEndDrawerChanged; 142 | final Widget? bottomSheet; 143 | final Color? backgroundColor; 144 | final bool? resizeToAvoidBottomInset; 145 | final bool? primary; 146 | final DragStartBehavior? drawerDragStartBehavior; 147 | final bool? extendBody; 148 | final Color? drawerScrimColor; 149 | final double? drawerEdgeDragWidth; 150 | final bool? drawerEnableOpenDragGesture; 151 | final bool? endDrawerEnableOpenDragGesture; 152 | final String? restorationId; 153 | final bool? extendBodyBehindAppBar; 154 | 155 | ScaffoldOptions({ 156 | this.key, 157 | this.floatingActionButton, 158 | this.floatingActionButtonLocation, 159 | this.floatingActionButtonAnimator, 160 | this.persistentFooterButtons, 161 | this.drawer, 162 | this.onDrawerChanged, 163 | this.endDrawer, 164 | this.onEndDrawerChanged, 165 | this.bottomSheet, 166 | this.backgroundColor, 167 | this.resizeToAvoidBottomInset, 168 | this.primary, 169 | this.drawerDragStartBehavior, 170 | this.extendBody, 171 | this.drawerScrimColor, 172 | this.drawerEdgeDragWidth, 173 | this.drawerEnableOpenDragGesture, 174 | this.endDrawerEnableOpenDragGesture, 175 | this.restorationId, 176 | this.extendBodyBehindAppBar, 177 | }); 178 | } 179 | 180 | class CommonBottomBarTheme { 181 | final Color? bgColor; 182 | final double? height; 183 | CommonBottomBarTheme({ 184 | this.bgColor, 185 | this.height, 186 | }); 187 | } 188 | 189 | @immutable 190 | class Material3BottomBarTheme extends CommonBottomBarTheme { 191 | final Duration? animationDuration; 192 | final NavigationDestinationLabelBehavior? labelBehavior; 193 | final double? elevation; 194 | 195 | Material3BottomBarTheme({ 196 | super.bgColor, 197 | super.height, 198 | this.animationDuration, 199 | this.labelBehavior, 200 | this.elevation, 201 | }); 202 | 203 | Material3BottomBarTheme copyWith({ 204 | Duration? animationDuration, 205 | NavigationDestinationLabelBehavior? labelBehavior, 206 | double? elevation, 207 | Color? bgColor, 208 | double? height, 209 | }) { 210 | return Material3BottomBarTheme( 211 | animationDuration: animationDuration ?? this.animationDuration, 212 | labelBehavior: labelBehavior ?? this.labelBehavior, 213 | elevation: elevation ?? this.elevation, 214 | bgColor: bgColor ?? this.bgColor, 215 | height: height ?? this.height, 216 | ); 217 | } 218 | } 219 | 220 | @immutable 221 | class CupertinoBottomBarTheme extends CommonBottomBarTheme { 222 | final Color? selectedItemColor; 223 | final Color? unselectedItemColor; 224 | final BottomNavigationBarType? type; 225 | final double iconSize; 226 | final Border? border; 227 | 228 | CupertinoBottomBarTheme({ 229 | super.bgColor, 230 | super.height, 231 | this.selectedItemColor, 232 | this.unselectedItemColor, 233 | this.type, 234 | this.border, 235 | this.iconSize = 24, 236 | }); 237 | 238 | CupertinoBottomBarTheme copyWith({ 239 | Color? selectedItemColor, 240 | Color? unselectedItemColor, 241 | Color? bgColor, 242 | BottomNavigationBarType? type, 243 | double? iconSize, 244 | double? height, 245 | Border? border, 246 | }) { 247 | return CupertinoBottomBarTheme( 248 | bgColor: bgColor ?? this.bgColor, 249 | height: height ?? this.height, 250 | selectedItemColor: selectedItemColor ?? this.selectedItemColor, 251 | unselectedItemColor: unselectedItemColor ?? this.unselectedItemColor, 252 | type: type ?? this.type, 253 | iconSize: iconSize ?? this.iconSize, 254 | border: border ?? this.border, 255 | ); 256 | } 257 | } 258 | 259 | @immutable 260 | class Material2BottomBarTheme extends CommonBottomBarTheme { 261 | final Color? selectedItemColor, unselectedItemColor; 262 | final BottomNavigationBarType? type; 263 | final IconThemeData? iconThemeData; 264 | final double? elevation; 265 | final double selectedFontSize, unselectedFontSize, iconSize; 266 | 267 | Material2BottomBarTheme({ 268 | super.bgColor, 269 | super.height, 270 | this.selectedItemColor, 271 | this.unselectedItemColor, 272 | this.type, 273 | this.iconThemeData, 274 | this.elevation, 275 | this.selectedFontSize = 14.0, 276 | this.unselectedFontSize = 12.0, 277 | this.iconSize = 24, 278 | }); 279 | 280 | Material2BottomBarTheme copyWith({ 281 | Color? selectedItemColor, 282 | Color? unselectedItemColor, 283 | Color? bgColor, 284 | BottomNavigationBarType? type, 285 | IconThemeData? iconThemeData, 286 | double? elevation, 287 | double? selectedFontSize, 288 | double? unselectedFontSize, 289 | double? iconSize, 290 | double? height, 291 | }) { 292 | return Material2BottomBarTheme( 293 | unselectedItemColor: unselectedItemColor ?? this.unselectedItemColor, 294 | type: type ?? this.type, 295 | iconThemeData: iconThemeData ?? this.iconThemeData, 296 | elevation: elevation ?? this.elevation, 297 | iconSize: iconSize ?? this.iconSize, 298 | bgColor: bgColor ?? this.bgColor, 299 | selectedItemColor: selectedItemColor ?? this.selectedItemColor, 300 | selectedFontSize: selectedFontSize ?? this.selectedFontSize, 301 | height: height ?? this.height, 302 | unselectedFontSize: unselectedFontSize ?? this.unselectedFontSize, 303 | ); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /lib/bart/bart_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:bart/bart/bart_bottombar_actions.dart'; 3 | import 'package:bart/bart/bart_model.dart'; 4 | import 'package:bart/bart/router_delegate.dart'; 5 | import 'package:bart/bart/widgets/animated_appbar.dart'; 6 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 7 | import 'package:bart/bart/widgets/nested_navigator.dart'; 8 | import 'package:bart/bart/widgets/side_bar/sidebar.dart'; 9 | import 'package:flutter/gestures.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | class BartScaffold extends StatefulWidget { 13 | final BartBottomBar bottomBar; 14 | final BartRouteBuilder routesBuilder; 15 | final String? initialRoute; 16 | final GlobalKey? navigationKey; 17 | 18 | /// Called when the current route changes 19 | final OnRouteChanged? onRouteChanged; 20 | // appBar 21 | final ValueNotifier appBarNotifier; 22 | final ValueNotifier showAppBarNotifier; 23 | final ValueNotifier showBottomBarNotifier; 24 | 25 | /// See all [Scaffold] options 26 | final ScaffoldOptions? scaffoldOptions; 27 | 28 | /// one of [CustomSideBarOptions] or [RailSideBarOptions] 29 | final SideBarOptions? sideBarOptions; 30 | 31 | BartScaffold({ 32 | super.key, 33 | required this.bottomBar, 34 | required this.routesBuilder, 35 | this.sideBarOptions, 36 | this.initialRoute, 37 | this.scaffoldOptions, 38 | this.onRouteChanged, 39 | this.navigationKey, 40 | bool showBottomBarOnStart = true, 41 | }) : appBarNotifier = ValueNotifier(null), 42 | showAppBarNotifier = ValueNotifier(false), 43 | showBottomBarNotifier = ValueNotifier(showBottomBarOnStart); 44 | 45 | @override 46 | State createState() => _BartScaffoldState(); 47 | } 48 | 49 | class _BartScaffoldState extends State 50 | with SingleTickerProviderStateMixin { 51 | late final GlobalKey navigationKey; 52 | final indexNotifier = ValueNotifier(0); 53 | final routingTypeNotifier = ValueNotifier(BartMenuRouteType.bottomNavigation); 54 | 55 | List get routesBuilder => widget.routesBuilder(); 56 | 57 | // int get initialIndex { 58 | // final index = routesBuilder 59 | // .indexWhere((element) => element.path == widget.initialRoute); 60 | // return index == -1 ? 0 : index; 61 | // } 62 | 63 | final RouteObserver routeObserver = RouteObserver(); 64 | 65 | @override 66 | void initState() { 67 | super.initState(); 68 | navigationKey = widget.navigationKey ?? GlobalKey(); 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return MenuRouter( 74 | initialRoute: widget.initialRoute, 75 | indexNotifier: indexNotifier, 76 | routesBuilder: widget.routesBuilder, 77 | navigationKey: navigationKey, 78 | routingTypeNotifier: routingTypeNotifier, 79 | onRouteChanged: widget.onRouteChanged, 80 | child: Actions( 81 | actions: >{ 82 | AppBarBuildIntent: BartAppBarAction(widget.appBarNotifier), 83 | AppBarAnimationIntent: 84 | BartAnimatedAppBarAction(widget.showAppBarNotifier), 85 | BottomBarIntent: BottomBarAction(widget.showBottomBarNotifier), 86 | }, 87 | child: AnimatedBuilder( 88 | animation: widget.appBarNotifier, 89 | builder: (context, child) { 90 | return Scaffold( 91 | appBar: AnimatedAppBar( 92 | appBar: widget.appBarNotifier.value, 93 | showStateNotifier: widget.showAppBarNotifier, 94 | ), 95 | backgroundColor: widget.scaffoldOptions?.backgroundColor, 96 | floatingActionButton: 97 | widget.scaffoldOptions?.floatingActionButton, 98 | floatingActionButtonLocation: 99 | widget.scaffoldOptions?.floatingActionButtonLocation, 100 | floatingActionButtonAnimator: 101 | widget.scaffoldOptions?.floatingActionButtonAnimator, 102 | persistentFooterButtons: 103 | widget.scaffoldOptions?.persistentFooterButtons, 104 | drawer: widget.scaffoldOptions?.drawer, 105 | onDrawerChanged: widget.scaffoldOptions?.onDrawerChanged, 106 | endDrawer: widget.scaffoldOptions?.endDrawer, 107 | onEndDrawerChanged: widget.scaffoldOptions?.onEndDrawerChanged, 108 | // 👾 bottom bar 109 | bottomNavigationBar: widget.sideBarOptions == null 110 | ? AnimatedBottomBar( 111 | bottomBar: widget.bottomBar, 112 | showStateNotifier: widget.showBottomBarNotifier, 113 | ) 114 | : null, 115 | // ---------------- 116 | bottomSheet: widget.scaffoldOptions?.bottomSheet, 117 | extendBodyBehindAppBar: 118 | widget.scaffoldOptions?.extendBodyBehindAppBar ?? true, 119 | drawerEdgeDragWidth: 120 | widget.scaffoldOptions?.drawerEdgeDragWidth, 121 | drawerScrimColor: widget.scaffoldOptions?.drawerScrimColor, 122 | drawerDragStartBehavior: 123 | widget.scaffoldOptions?.drawerDragStartBehavior ?? 124 | DragStartBehavior.start, 125 | primary: widget.scaffoldOptions?.primary ?? true, 126 | drawerEnableOpenDragGesture: 127 | widget.scaffoldOptions?.drawerEnableOpenDragGesture ?? true, 128 | endDrawerEnableOpenDragGesture: 129 | widget.scaffoldOptions?.endDrawerEnableOpenDragGesture ?? 130 | true, 131 | extendBody: widget.scaffoldOptions?.extendBody ?? false, 132 | resizeToAvoidBottomInset: 133 | widget.scaffoldOptions?.resizeToAvoidBottomInset, 134 | restorationId: widget.scaffoldOptions?.restorationId, 135 | key: widget.scaffoldOptions?.key, 136 | body: NestedNavigator( 137 | navigationKey: navigationKey, 138 | routes: routesBuilder, 139 | initialRoute: widget.initialRoute, 140 | navigatorObserver: routeObserver, 141 | appBarNotifier: widget.appBarNotifier, 142 | showAppBarNotifier: widget.showAppBarNotifier, 143 | parentContext: context, 144 | sideBarOptions: widget.sideBarOptions, 145 | ), 146 | ); 147 | }), 148 | ), 149 | ); 150 | } 151 | } 152 | 153 | /// Use this intent to change the current index 154 | class BottomBarIndexIntent extends Intent { 155 | final int index; 156 | 157 | const BottomBarIndexIntent(this.index); 158 | } 159 | -------------------------------------------------------------------------------- /lib/bart/router_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 5 | import 'package:bart/bart/widgets/nested_navigator.dart'; 6 | import 'package:universal_html/html.dart'; 7 | 8 | import 'bart_model.dart'; 9 | 10 | typedef OnRouteChanged = void Function(BartMenuRoute route); 11 | 12 | class MenuRouter extends InheritedWidget { 13 | final BartRouteBuilder routesBuilder; 14 | final GlobalKey navigationKey; 15 | final ValueNotifier indexNotifier; 16 | final ValueNotifier routingTypeNotifier; 17 | final OnRouteChanged? onRouteChanged; 18 | 19 | MenuRouter({ 20 | super.key, 21 | String? initialRoute, 22 | required this.routesBuilder, 23 | required this.navigationKey, 24 | required this.indexNotifier, 25 | required this.routingTypeNotifier, 26 | this.onRouteChanged, 27 | required super.child, 28 | }) { 29 | if (initialRoute != null) { 30 | final index = _currentIndex(initialRoute); 31 | indexNotifier.value = index; 32 | } 33 | } 34 | 35 | void updateRoute(String path) { 36 | final route = _currentRoute(path); 37 | final index = _currentIndex(path); 38 | if(indexNotifier.value == index) { 39 | return; 40 | } 41 | 42 | handleWebUrl(route); 43 | WidgetsBinding.instance.addPostFrameCallback((_) { 44 | indexNotifier.value = index; 45 | routingTypeNotifier.value = route.type; 46 | onRouteChanged?.call(route); 47 | }); 48 | } 49 | 50 | void handleWebUrl(BartMenuRoute route) { 51 | if (!kIsWeb) { 52 | return; 53 | } 54 | final uri = Uri(path: route.path); 55 | window.history.pushState(null, '', uri.toString()); 56 | } 57 | 58 | int _currentIndex(String path) { 59 | 60 | final extractedPath = path.split('/') 61 | ..removeWhere((element) => element.isEmpty); 62 | 63 | String domain; 64 | if (extractedPath.length > 1) { 65 | domain = extractedPath.first; 66 | } else { 67 | domain = path; 68 | } 69 | 70 | return routesBuilder().indexWhere( 71 | (element) => removePath(element.path) == removePath(domain), 72 | ); 73 | } 74 | 75 | String removePath(String path) => path.replaceAll('/', ''); 76 | 77 | static MenuRouter of(BuildContext context) => 78 | context.dependOnInheritedWidgetOfExactType()!; 79 | 80 | @override 81 | bool updateShouldNotify(MenuRouter oldWidget) { 82 | return true; 83 | } 84 | 85 | BartMenuRoute _currentRoute(String path) { 86 | return routesBuilder().firstWhere( 87 | (element) => element.path == path, 88 | orElse: () => routesBuilder().first, 89 | ); 90 | } 91 | } 92 | 93 | class RouteAwareWidget extends StatefulWidget { 94 | final BartMenuRoute route; 95 | final Widget child; 96 | final ValueNotifier appBarNotifier; 97 | final ValueNotifier showAppBarNotifier; 98 | 99 | const RouteAwareWidget({ 100 | super.key, 101 | required this.route, 102 | required this.child, 103 | required this.appBarNotifier, 104 | required this.showAppBarNotifier, 105 | }); 106 | 107 | @override 108 | State createState() => _RouteAwareWidgetState(); 109 | } 110 | 111 | class _RouteAwareWidgetState extends State with RouteAware { 112 | @override 113 | void didChangeDependencies() { 114 | super.didChangeDependencies(); 115 | routeObserver.subscribe(this, ModalRoute.of(context)); 116 | } 117 | 118 | @override 119 | void dispose() { 120 | routeObserver.unsubscribe(this); 121 | super.dispose(); 122 | } 123 | 124 | @override 125 | void didPush() { 126 | MenuRouter.of(context).updateRoute(widget.route.path); 127 | } 128 | 129 | @override 130 | void didPopNext() { 131 | MenuRouter.of(context).updateRoute(widget.route.path); 132 | } 133 | 134 | // @override 135 | // void didPop() { 136 | 137 | // } 138 | 139 | @override 140 | Widget build(BuildContext context) => widget.child; 141 | } 142 | -------------------------------------------------------------------------------- /lib/bart/widgets/animated_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Use this intent when using animated appBar to show/hide using animation 5 | class AppBarAnimationIntent extends Intent { 6 | final bool state; 7 | 8 | const AppBarAnimationIntent(this.state); 9 | 10 | factory AppBarAnimationIntent.show() => const AppBarAnimationIntent(true); 11 | 12 | factory AppBarAnimationIntent.hide() => const AppBarAnimationIntent(false); 13 | } 14 | 15 | /// se this action when using animated appBar to show/hide using [AppBarBuildIntent] 16 | class BartAnimatedAppBarAction extends Action { 17 | ValueNotifier show; 18 | 19 | BartAnimatedAppBarAction(this.show); 20 | 21 | @override 22 | void invoke(covariant AppBarAnimationIntent intent) { 23 | show.value = intent.state; 24 | } 25 | } 26 | 27 | class AnimatedAppBar extends StatelessWidget implements PreferredSizeWidget { 28 | final PreferredSizeWidget? appBar; 29 | final ValueNotifier showStateNotifier; 30 | 31 | const AnimatedAppBar({ 32 | super.key, 33 | required this.appBar, 34 | required this.showStateNotifier, 35 | }); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return ValueListenableBuilder( 40 | valueListenable: showStateNotifier, 41 | builder: (context, show, child) => Stack( 42 | children: [ 43 | AnimatedPositioned( 44 | curve: Curves.decelerate, 45 | duration: const Duration(milliseconds: 300), 46 | top: show ? 0 : -150, 47 | left: 0, 48 | right: 0, 49 | child: appBar ?? Container(), 50 | ), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | @override 57 | Size get preferredSize => 58 | appBar == null ? const Size(0, 0) : appBar!.preferredSize; 59 | } 60 | -------------------------------------------------------------------------------- /lib/bart/widgets/bottom_bar/bottom_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:bart/bart/router_delegate.dart'; 3 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_cupertino.dart'; 4 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_custom.dart'; 5 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_material.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:universal_io/io.dart'; 9 | 10 | typedef BottomBarTapAction = void Function(int index); 11 | 12 | typedef BartRouteBuilder = List Function(); 13 | 14 | enum Theme { material, material3, cupertino, custom } 15 | 16 | class BartBottomBar extends StatefulWidget { 17 | final int currentIndex; 18 | final Theme theme; 19 | final BartBottomBarFactory? bottomBarCustom; 20 | final CommonBottomBarTheme? bottomBarTheme; 21 | final bool enableHapticFeedback; 22 | 23 | const BartBottomBar._({ 24 | this.enableHapticFeedback = true, 25 | this.bottomBarCustom, 26 | this.bottomBarTheme, 27 | required this.theme, 28 | required this.currentIndex, 29 | }); 30 | 31 | factory BartBottomBar.material({ 32 | bool enableHapticFeedback = true, 33 | int index = 0, 34 | Color? bgColor, 35 | Color? selectedItemColor, 36 | Color? unselectedItemColor, 37 | double? height, 38 | BottomNavigationBarType? type, 39 | IconThemeData? iconThemeData, 40 | double selectedFontSize = 14.0, 41 | double unselectedFontSize = 12.0, 42 | double iconSize = 24, 43 | double? elevation, 44 | }) => 45 | BartBottomBar._( 46 | enableHapticFeedback: enableHapticFeedback, 47 | bottomBarTheme: Material2BottomBarTheme().copyWith( 48 | bgColor: bgColor, 49 | selectedItemColor: selectedItemColor, 50 | unselectedItemColor: unselectedItemColor, 51 | type: type, 52 | iconThemeData: iconThemeData, 53 | elevation: elevation, 54 | height: height, 55 | iconSize: iconSize, 56 | selectedFontSize: selectedFontSize, 57 | unselectedFontSize: unselectedFontSize, 58 | ), 59 | theme: Theme.material, 60 | currentIndex: index, 61 | ); 62 | 63 | factory BartBottomBar.material3({ 64 | Duration? animationDuration, 65 | NavigationDestinationLabelBehavior? labelBehavior, 66 | double? elevation, 67 | Color? bgColor, 68 | double? height, 69 | bool enableHapticFeedback = true, 70 | int index = 0, 71 | }) => 72 | BartBottomBar._( 73 | enableHapticFeedback: enableHapticFeedback, 74 | bottomBarTheme: Material3BottomBarTheme().copyWith( 75 | animationDuration: animationDuration, 76 | labelBehavior: labelBehavior, 77 | elevation: elevation, 78 | bgColor: bgColor, 79 | height: height, 80 | ), 81 | theme: Theme.material3, 82 | currentIndex: index, 83 | ); 84 | 85 | factory BartBottomBar.custom({ 86 | required BartBottomBarFactory bottomBarFactory, 87 | int index = 0, 88 | }) => 89 | BartBottomBar._( 90 | theme: Theme.custom, 91 | bottomBarCustom: bottomBarFactory, 92 | currentIndex: index, 93 | ); 94 | 95 | factory BartBottomBar.cupertino({ 96 | Color? bgColor, 97 | Color? selectedItemColor, 98 | Color? unselectedItemColor, 99 | BottomNavigationBarType? type, 100 | double iconSize = 24, 101 | double? height, 102 | Border? border, 103 | bool enableHapticFeedback = true, 104 | int index = 0, 105 | }) => 106 | BartBottomBar._( 107 | bottomBarTheme: CupertinoBottomBarTheme().copyWith( 108 | bgColor: bgColor, 109 | selectedItemColor: selectedItemColor, 110 | unselectedItemColor: unselectedItemColor, 111 | type: type, 112 | iconSize: iconSize, 113 | height: height, 114 | border: border, 115 | ), 116 | enableHapticFeedback: enableHapticFeedback, 117 | theme: Theme.cupertino, 118 | currentIndex: index, 119 | ); 120 | 121 | factory BartBottomBar.adaptive({ 122 | Material3BottomBarTheme? materialBottomBarTheme, 123 | CupertinoBottomBarTheme? cupertinoBottomBarTheme, 124 | bool enableHapticFeedback = true, 125 | int index = 0, 126 | }) => 127 | BartBottomBar._( 128 | bottomBarTheme: Platform.isIOS 129 | ? cupertinoBottomBarTheme ?? CupertinoBottomBarTheme() 130 | : materialBottomBarTheme ?? Material3BottomBarTheme(), 131 | enableHapticFeedback: enableHapticFeedback, 132 | theme: Platform.isIOS ? Theme.cupertino : Theme.material3, 133 | currentIndex: index, 134 | ); 135 | 136 | @override 137 | BartBottomBarState createState() => BartBottomBarState(); 138 | } 139 | 140 | @visibleForTesting 141 | class BartBottomBarState extends State { 142 | List get routes => MenuRouter.of(context).routesBuilder(); 143 | ValueNotifier get currentIndexNotifier => 144 | MenuRouter.of(context).indexNotifier; 145 | ValueNotifier get currentRoutingTypeNotifier => 146 | MenuRouter.of(context).routingTypeNotifier; 147 | 148 | void onTap(int index) { 149 | if (currentIndexNotifier.value == index && 150 | currentRoutingTypeNotifier.value == 151 | BartMenuRouteType.bottomNavigation) { 152 | return; 153 | } 154 | 155 | if (widget.enableHapticFeedback) { 156 | HapticFeedback.selectionClick(); 157 | } 158 | final nestedContext = MenuRouter.of(context).navigationKey.currentContext; 159 | if (nestedContext != null) { 160 | Navigator.of(nestedContext).pushReplacementNamed(routes[index].path); 161 | } 162 | } 163 | 164 | List get mainRoutes => routes 165 | .where((route) => route.type == BartMenuRouteType.bottomNavigation) 166 | .toList(); 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | switch (widget.theme) { 171 | case Theme.cupertino: 172 | return BartCupertinoBottomBar( 173 | routes: mainRoutes, 174 | theme: widget.bottomBarTheme! as CupertinoBottomBarTheme, 175 | currentIndexNotifier: currentIndexNotifier, 176 | onTap: onTap, 177 | ); 178 | case Theme.material: 179 | return BartMaterialBottomBar( 180 | routes: mainRoutes, 181 | theme: widget.bottomBarTheme! as Material2BottomBarTheme, 182 | currentIndexNotifier: currentIndexNotifier, 183 | onTap: onTap, 184 | ); 185 | case Theme.material3: 186 | return BartMaterial3BottomBar( 187 | routes: routes, 188 | theme: widget.bottomBarTheme! as Material3BottomBarTheme, 189 | currentIndexNotifier: currentIndexNotifier, 190 | onTap: onTap, 191 | ); 192 | default: 193 | return ValueListenableBuilder( 194 | valueListenable: currentIndexNotifier, 195 | builder: (context, int index, child) { 196 | return widget.bottomBarCustom!.create( 197 | context: context, 198 | routes: mainRoutes, 199 | onTap: onTap, 200 | currentIndex: index, 201 | ); 202 | }); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /lib/bart/widgets/bottom_bar/bottombar_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | typedef NotificationBuilder = Widget Function( 4 | BuildContext context, 5 | ); 6 | 7 | class BottomBarIcon extends StatelessWidget { 8 | final Icon icon; 9 | final NotificationBuilder? notificationBuilder; 10 | final double? top; 11 | final double? right; 12 | final Text? notificationLabel; 13 | final Color? notificationColor; 14 | 15 | const BottomBarIcon({ 16 | super.key, 17 | required this.icon, 18 | this.notificationBuilder, 19 | this.top, 20 | this.right, 21 | this.notificationLabel, 22 | this.notificationColor, 23 | }); 24 | 25 | factory BottomBarIcon.builder({ 26 | required Icon icon, 27 | required NotificationBuilder notificationBuilder, 28 | double? top, 29 | double? right, 30 | }) => 31 | BottomBarIcon( 32 | icon: icon, 33 | notificationBuilder: notificationBuilder, 34 | top: top, 35 | right: right, 36 | ); 37 | 38 | factory BottomBarIcon.withNotification({ 39 | required Icon icon, 40 | required Text notificationLabel, 41 | required Color notificationColor, 42 | double? top, 43 | double? right, 44 | }) => 45 | BottomBarIcon( 46 | icon: icon, 47 | top: top, 48 | right: right, 49 | notificationLabel: notificationLabel, 50 | notificationColor: notificationColor, 51 | ); 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return Stack(children: [ 56 | icon, 57 | Positioned( 58 | top: top ?? -1.0, 59 | right: right ?? -1.0, 60 | child: Stack( 61 | children: [ 62 | if (notificationBuilder != null) notificationBuilder!(context), 63 | if (notificationBuilder == null) 64 | Container( 65 | decoration: BoxDecoration( 66 | color: notificationColor, 67 | shape: BoxShape.circle, 68 | ), 69 | padding: const EdgeInsets.all(4), 70 | child: notificationLabel, 71 | ), 72 | ], 73 | ), 74 | ) 75 | ]); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/bart/widgets/bottom_bar/styles/bottom_bar_cupertino.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | class BartCupertinoBottomBar extends StatefulWidget { 6 | final List routes; 7 | final BottomBarTapAction onTap; 8 | final ValueNotifier currentIndexNotifier; 9 | 10 | final CupertinoBottomBarTheme theme; 11 | 12 | const BartCupertinoBottomBar({ 13 | super.key, 14 | required this.routes, 15 | required this.onTap, 16 | required this.currentIndexNotifier, 17 | required this.theme, 18 | }); 19 | 20 | @override 21 | State createState() => _BartCupertinoBottomBarState(); 22 | } 23 | 24 | class _BartCupertinoBottomBarState extends State { 25 | List? _items; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ValueListenableBuilder( 30 | valueListenable: widget.currentIndexNotifier, 31 | builder: ((context, int index, child) { 32 | _items = buildRouteWidgetList(context); 33 | return CupertinoTabBar( 34 | key: const ValueKey('bottom_bar'), 35 | items: _items!, 36 | currentIndex: index, 37 | iconSize: widget.theme.iconSize, 38 | border: widget.theme.border, 39 | backgroundColor: widget.theme.bgColor, 40 | activeColor: widget.theme.selectedItemColor, 41 | height: widget.theme.height ?? 50.0, 42 | inactiveColor: 43 | widget.theme.unselectedItemColor ?? CupertinoColors.inactiveGray, 44 | onTap: (index) => widget.onTap(index), 45 | ); 46 | }), 47 | ); 48 | } 49 | 50 | List buildRouteWidgetList(BuildContext context) { 51 | return widget.routes 52 | .where((element) => element.type == BartMenuRouteType.bottomNavigation) 53 | .map( 54 | (route) { 55 | final routeIndex = widget.routes.indexOf(route); 56 | 57 | if (route.icon != null) { 58 | return BottomNavigationBarItem( 59 | icon: Icon(route.icon), 60 | label: route.label, 61 | ); 62 | } else if (route.iconBuilder != null) { 63 | return BottomNavigationBarItem( 64 | icon: route.iconBuilder!( 65 | context, 66 | routeIndex == widget.currentIndexNotifier.value, 67 | ), 68 | label: route.label, 69 | ); 70 | } 71 | throw Exception( 72 | "You must provide an icon or an iconBuilder for each route", 73 | ); 74 | }, 75 | ).toList(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/bart/widgets/bottom_bar/styles/bottom_bar_custom.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | abstract class BartBottomBarFactory { 5 | const BartBottomBarFactory(); 6 | 7 | @factory 8 | Widget create({ 9 | required BuildContext context, 10 | required List routes, 11 | required void Function(int) onTap, 12 | required final int currentIndex, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/bart/widgets/bottom_bar/styles/bottom_bar_material.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class BartMaterialBottomBar extends StatefulWidget { 6 | final List routes; 7 | final BottomBarTapAction onTap; 8 | final Material2BottomBarTheme theme; 9 | final ValueNotifier currentIndexNotifier; 10 | 11 | const BartMaterialBottomBar({ 12 | super.key, 13 | required this.routes, 14 | required this.onTap, 15 | required this.currentIndexNotifier, 16 | required this.theme, 17 | }); 18 | 19 | @override 20 | State createState() => _BartMaterialBottomBarState(); 21 | } 22 | 23 | class _BartMaterialBottomBarState extends State { 24 | @override 25 | Widget build(BuildContext context) { 26 | return ValueListenableBuilder( 27 | valueListenable: widget.currentIndexNotifier, 28 | builder: (context, int index, child) { 29 | return SizedBox( 30 | height: widget.theme.height, 31 | child: BottomNavigationBar( 32 | items: routeWidgetList, 33 | currentIndex: widget.currentIndexNotifier.value, 34 | elevation: widget.theme.elevation, 35 | iconSize: widget.theme.iconSize, 36 | backgroundColor: widget.theme.bgColor, 37 | type: widget.theme.type ?? BottomNavigationBarType.fixed, 38 | selectedIconTheme: widget.theme.iconThemeData, 39 | selectedItemColor: widget.theme.selectedItemColor, 40 | unselectedItemColor: widget.theme.unselectedItemColor, 41 | selectedFontSize: widget.theme.selectedFontSize, 42 | unselectedFontSize: widget.theme.unselectedFontSize, 43 | onTap: (index) => widget.onTap(index), 44 | ), 45 | ); 46 | }, 47 | ); 48 | } 49 | 50 | List get routeWidgetList => widget.routes 51 | .map((route) => BottomNavigationBarItem( 52 | icon: Icon(route.icon), 53 | label: route.label, 54 | )) 55 | .toList(); 56 | } 57 | 58 | class BartMaterial3BottomBar extends StatefulWidget { 59 | final List routes; 60 | final BottomBarTapAction onTap; 61 | final Material3BottomBarTheme theme; 62 | final ValueNotifier currentIndexNotifier; 63 | 64 | const BartMaterial3BottomBar({ 65 | super.key, 66 | required this.routes, 67 | required this.onTap, 68 | required this.currentIndexNotifier, 69 | required this.theme, 70 | }); 71 | 72 | @override 73 | State createState() => _BartMaterial3BottomBarState(); 74 | } 75 | 76 | class _BartMaterial3BottomBarState extends State { 77 | @override 78 | Widget build(BuildContext context) { 79 | return ValueListenableBuilder( 80 | valueListenable: widget.currentIndexNotifier, 81 | builder: (context, int index, child) { 82 | return NavigationBar( 83 | selectedIndex: index, 84 | destinations: getRouteWidgetList(context), 85 | elevation: widget.theme.elevation, 86 | backgroundColor: widget.theme.bgColor, 87 | height: widget.theme.height, 88 | onDestinationSelected: widget.onTap, 89 | animationDuration: widget.theme.animationDuration, 90 | labelBehavior: widget.theme.labelBehavior, 91 | ); 92 | }, 93 | ); 94 | } 95 | 96 | List getRouteWidgetList(BuildContext context) => 97 | widget.routes 98 | .where( 99 | (element) => element.type == BartMenuRouteType.bottomNavigation) 100 | .map( 101 | (route) { 102 | final routeIndex = widget.routes.indexOf(route); 103 | if (route.icon != null) { 104 | return NavigationDestination( 105 | icon: Icon(route.icon), 106 | label: route.label ?? '', 107 | selectedIcon: 108 | route.selectedIcon != null ? Icon(route.selectedIcon) : null, 109 | ); 110 | } else if (route.iconBuilder != null) { 111 | return NavigationDestination( 112 | icon: route.iconBuilder!( 113 | context, 114 | routeIndex == widget.currentIndexNotifier.value, 115 | ), 116 | label: route.label ?? '', 117 | // selectedIcon: route.selectedIconBuilder != null 118 | // ? route.selectedIconBuilder!(context) 119 | // : null, 120 | ); 121 | } 122 | throw Exception( 123 | "You must provide an icon or an iconBuilder for each route", 124 | ); 125 | }, 126 | ).toList(); 127 | } 128 | -------------------------------------------------------------------------------- /lib/bart/widgets/nested_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:bart/bart/bart_bottombar_actions.dart'; 3 | import 'package:bart/bart/bart_model.dart'; 4 | import 'package:bart/bart/router_delegate.dart'; 5 | import 'package:bart/bart/widgets/side_bar/custom_sidebar.dart'; 6 | import 'package:bart/bart/widgets/side_bar/rail_sidebar.dart'; 7 | import 'package:bart/bart/widgets/side_bar/sidebar.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | final RouteObserver routeObserver = RouteObserver(); 12 | 13 | class NestedNavigator extends StatefulWidget { 14 | final BuildContext parentContext; 15 | final GlobalKey navigationKey; 16 | final RouteObserver navigatorObserver; 17 | final ValueNotifier appBarNotifier; 18 | final ValueNotifier showAppBarNotifier; 19 | final String? initialRoute; 20 | final List routes; 21 | final Function()? onWillPop; 22 | final SideBarOptions? sideBarOptions; 23 | 24 | const NestedNavigator({ 25 | super.key, 26 | required this.parentContext, 27 | required this.navigationKey, 28 | required this.appBarNotifier, 29 | required this.showAppBarNotifier, 30 | this.initialRoute, 31 | required this.routes, 32 | required this.navigatorObserver, 33 | this.sideBarOptions, 34 | this.onWillPop, 35 | }); 36 | 37 | @override 38 | State createState() => _NestedNavigatorState(); 39 | } 40 | 41 | class _NestedNavigatorState extends State 42 | with AppBarNotifier, BartNotifier { 43 | final Map pageCache = {}; 44 | final PageStorageBucket bucket = PageStorageBucket(); 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final content = PopScope( 49 | child: Navigator( 50 | key: widget.navigationKey, 51 | initialRoute: widget.initialRoute, 52 | // observers: [routeObserver()], 53 | onGenerateRoute: (RouteSettings routeSettings) { 54 | Actions.invoke(context, AppBarBuildIntent.empty()); 55 | hideAppBar(context); 56 | 57 | final route = widget.routes.firstWhere( 58 | (element) => element.path == routeSettings.name, 59 | orElse: () => widget.routes.first, 60 | ); 61 | 62 | if (route.showBottomBar) { 63 | showBottomBar(context); 64 | } else { 65 | hideBottomBar(context); 66 | } 67 | 68 | return PageRouteBuilder( 69 | maintainState: route.maintainState ?? true, 70 | transitionDuration: 71 | route.transitionDuration ?? const Duration(milliseconds: 300), 72 | transitionsBuilder: 73 | route.transitionsBuilder ?? (_, a, b, child) => child, 74 | pageBuilder: (context, __, ___) { 75 | if (route.cache) { 76 | if (!pageCache.containsKey(route.path)) { 77 | pageCache[route.path] = RouteAwareWidget( 78 | appBarNotifier: widget.appBarNotifier, 79 | showAppBarNotifier: widget.showAppBarNotifier, 80 | route: route, 81 | child: route.pageBuilder( 82 | widget.parentContext, 83 | context, 84 | routeSettings, 85 | ), 86 | ); 87 | } 88 | 89 | return PageStorage( 90 | bucket: bucket, 91 | child: pageCache[route.path]!, 92 | ); 93 | } 94 | 95 | return RouteAwareWidget( 96 | appBarNotifier: widget.appBarNotifier, 97 | showAppBarNotifier: widget.showAppBarNotifier, 98 | route: route, 99 | child: route.pageBuilder( 100 | widget.parentContext, 101 | context, 102 | routeSettings, 103 | ), 104 | ); 105 | }, 106 | ); 107 | }, 108 | ), 109 | onPopInvoked: (willPop) { 110 | if (!willPop) { 111 | return; 112 | } 113 | showBottomBar(context); 114 | if (widget.onWillPop != null) { 115 | widget.onWillPop!(); 116 | } 117 | if (widget.navigationKey.currentState != null && 118 | widget.navigationKey.currentState!.canPop()) { 119 | widget.navigationKey.currentState!.pop(); 120 | } 121 | }, 122 | ); 123 | return switch ((kIsWeb, widget.sideBarOptions)) { 124 | (_, CustomSideBarOptions option) => CustomSideBarContainer( 125 | gravity: option.gravity, 126 | routes: widget.routes, 127 | sideBarBuilder: option.sideBarBuilder, 128 | child: content, 129 | ), 130 | (_, RailSideBarOptions option) => WebRailSideBarContainer( 131 | gravity: option.gravity, 132 | extended: option.extended, 133 | routes: widget.routes, 134 | child: content, 135 | ), 136 | (_, _) => content, 137 | }; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/bart/widgets/side_bar/custom_sidebar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:bart/bart/router_delegate.dart'; 3 | import 'package:bart/bart/widgets/side_bar/sidebar.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CustomSideBarContainer extends StatefulWidget { 7 | final Widget child; 8 | final List routes; 9 | final Gravity gravity; 10 | final SideBarBuilder sideBarBuilder; 11 | 12 | const CustomSideBarContainer({ 13 | super.key, 14 | required this.child, 15 | required this.routes, 16 | required this.sideBarBuilder, 17 | this.gravity = Gravity.left, 18 | }); 19 | 20 | @override 21 | State createState() => _CustomSideBarContainerState(); 22 | } 23 | 24 | typedef SideBarBuilder = Widget Function( 25 | List routes, 26 | OnTapItem onTapItem, 27 | ValueNotifier currentItem, 28 | ); 29 | 30 | typedef OnTapItem = void Function(int index); 31 | 32 | class _CustomSideBarContainerState extends State { 33 | late List routes = []; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | routes = widget.routes 39 | .where( 40 | (element) => element.type == BartMenuRouteType.bottomNavigation, 41 | ) 42 | .toList(); 43 | } 44 | 45 | @override 46 | void didChangeDependencies() { 47 | super.didChangeDependencies(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Row( 53 | children: [ 54 | if (widget.gravity == Gravity.right) Flexible(child: widget.child), 55 | widget.sideBarBuilder( 56 | routes, 57 | onTapItem, 58 | MenuRouter.of(context).indexNotifier, 59 | ), 60 | if (widget.gravity == Gravity.left) Flexible(child: widget.child), 61 | ], 62 | ); 63 | } 64 | 65 | void onTapItem(int index) { 66 | final nestedContext = MenuRouter.of(context) // 67 | .navigationKey 68 | .currentContext; 69 | if (nestedContext != null) { 70 | Navigator.of(nestedContext).pushReplacementNamed( 71 | widget.routes[index].path, 72 | ); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/bart/widgets/side_bar/rail_sidebar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_model.dart'; 2 | import 'package:bart/bart/router_delegate.dart'; 3 | import 'package:bart/bart/widgets/side_bar/sidebar.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class WebRailSideBarContainer extends StatefulWidget { 7 | final Widget child; 8 | final List routes; 9 | final Gravity gravity; 10 | final bool extended; 11 | 12 | const WebRailSideBarContainer({ 13 | super.key, 14 | required this.child, 15 | required this.routes, 16 | this.gravity = Gravity.left, 17 | this.extended = false, 18 | }); 19 | 20 | @override 21 | State createState() => 22 | _WebRailSideBarContainerState(); 23 | } 24 | 25 | class _WebRailSideBarContainerState extends State { 26 | int selectedIndex = 0; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | WidgetsBinding.instance.addPostFrameCallback((_) { 32 | selectedIndex = MenuRouter.of(context).indexNotifier.value; 33 | }); 34 | } 35 | 36 | @override 37 | void didChangeDependencies() { 38 | super.didChangeDependencies(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Row( 44 | children: [ 45 | if (widget.gravity == Gravity.right) Flexible(child: widget.child), 46 | ValueListenableBuilder( 47 | valueListenable: MenuRouter.of(context).indexNotifier, 48 | builder: (context, int index, child) => NavigationRail( 49 | selectedIndex: index, 50 | extended: widget.extended, 51 | onDestinationSelected: (index) { 52 | final nestedContext = MenuRouter.of(context) // 53 | .navigationKey 54 | .currentContext; 55 | if (nestedContext != null) { 56 | Navigator.of(nestedContext).pushReplacementNamed( 57 | widget.routes[index].path, 58 | ); 59 | } 60 | }, 61 | destinations: widget.routes 62 | .where((element) => 63 | element.type == BartMenuRouteType.bottomNavigation) 64 | .map( 65 | (route) { 66 | if (route.icon != null) { 67 | return NavigationRailDestination( 68 | icon: Icon(route.icon), 69 | selectedIcon: route.selectedIcon != null 70 | ? Icon(route.selectedIcon) 71 | : Icon(route.icon), 72 | label: Text(route.label ?? ''), 73 | ); 74 | } else if (route.iconBuilder != null) { 75 | final routeIndex = widget.routes.indexOf(route); 76 | return NavigationRailDestination( 77 | icon: route.iconBuilder!( 78 | context, 79 | routeIndex == MenuRouter.of(context).indexNotifier.value, 80 | ), 81 | label: Text(route.label ?? ''), 82 | ); 83 | } else { 84 | throw Exception( 85 | "You must provide an icon or an iconBuilder for each route (route: ${route.path}))", 86 | ); 87 | } 88 | }, 89 | ).toList(), 90 | ), 91 | ), 92 | if (widget.gravity == Gravity.left) Flexible(child: widget.child), 93 | ], 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/bart/widgets/side_bar/sidebar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/widgets/side_bar/custom_sidebar.dart'; 2 | 3 | enum Gravity { 4 | /// shows the sidebar on the left side of the screen 5 | left, 6 | 7 | /// shows the sidebar on the right side of the screen 8 | right, 9 | } 10 | 11 | sealed class SideBarOptions { 12 | final Gravity gravity; 13 | 14 | SideBarOptions({ 15 | this.gravity = Gravity.left, 16 | }); 17 | } 18 | 19 | class CustomSideBarOptions extends SideBarOptions { 20 | final SideBarBuilder sideBarBuilder; 21 | 22 | CustomSideBarOptions({ 23 | required this.sideBarBuilder, 24 | super.gravity, 25 | }); 26 | } 27 | 28 | class RailSideBarOptions extends SideBarOptions { 29 | final bool extended; 30 | 31 | RailSideBarOptions({ 32 | this.extended = false, 33 | super.gravity, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | charcode: 29 | dependency: transitive 30 | description: 31 | name: charcode 32 | sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.1" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.18.0" 52 | csslib: 53 | dependency: transitive 54 | description: 55 | name: csslib 56 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.0.0" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.3.1" 68 | flutter: 69 | dependency: "direct main" 70 | description: flutter 71 | source: sdk 72 | version: "0.0.0" 73 | flutter_lints: 74 | dependency: "direct dev" 75 | description: 76 | name: flutter_lints 77 | sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "3.0.1" 81 | flutter_test: 82 | dependency: "direct dev" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | html: 87 | dependency: transitive 88 | description: 89 | name: html 90 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.15.4" 94 | lints: 95 | dependency: transitive 96 | description: 97 | name: lints 98 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "3.0.0" 102 | matcher: 103 | dependency: transitive 104 | description: 105 | name: matcher 106 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.12.16" 110 | material_color_utilities: 111 | dependency: transitive 112 | description: 113 | name: material_color_utilities 114 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "0.5.0" 118 | meta: 119 | dependency: transitive 120 | description: 121 | name: meta 122 | sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "1.10.0" 126 | path: 127 | dependency: transitive 128 | description: 129 | name: path 130 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "1.8.3" 134 | sky_engine: 135 | dependency: transitive 136 | description: flutter 137 | source: sdk 138 | version: "0.0.99" 139 | source_span: 140 | dependency: transitive 141 | description: 142 | name: source_span 143 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "1.10.0" 147 | stack_trace: 148 | dependency: transitive 149 | description: 150 | name: stack_trace 151 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.11.1" 155 | stream_channel: 156 | dependency: transitive 157 | description: 158 | name: stream_channel 159 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "2.1.2" 163 | string_scanner: 164 | dependency: transitive 165 | description: 166 | name: string_scanner 167 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.2.0" 171 | term_glyph: 172 | dependency: transitive 173 | description: 174 | name: term_glyph 175 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "1.2.1" 179 | test_api: 180 | dependency: transitive 181 | description: 182 | name: test_api 183 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "0.6.1" 187 | typed_data: 188 | dependency: transitive 189 | description: 190 | name: typed_data 191 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "1.3.2" 195 | universal_html: 196 | dependency: "direct main" 197 | description: 198 | name: universal_html 199 | sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "2.2.4" 203 | universal_io: 204 | dependency: "direct main" 205 | description: 206 | name: universal_io 207 | sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "2.2.2" 211 | vector_math: 212 | dependency: transitive 213 | description: 214 | name: vector_math 215 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "2.1.4" 219 | web: 220 | dependency: transitive 221 | description: 222 | name: web 223 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "0.3.0" 227 | sdks: 228 | dart: ">=3.2.0-194.0.dev <4.0.0" 229 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bart 2 | description: A bottom navigation bar using navigator 2 for switching tabs 3 | version: 1.4.2 4 | homepage: https://en.apparence.io 5 | repository: https://github.com/Apparence-io/bart 6 | 7 | environment: 8 | sdk: ">=3.1.0 <4.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | universal_html: ^2.2.4 14 | universal_io: ^2.2.2 15 | 16 | dev_dependencies: 17 | flutter_lints: ^3.0.0 18 | flutter_test: 19 | sdk: flutter 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | # The following section is specific to Flutter. 24 | flutter: null 25 | # To add assets to your package, add an assets section, like this: 26 | # assets: 27 | # - images/a_dot_burr.jpeg 28 | # - images/a_dot_ham.jpeg 29 | # 30 | # For details regarding assets in packages, see 31 | # https://flutter.dev/assets-and-images/#from-packages 32 | # 33 | # An image asset can refer to one or more resolution-specific "variants", see 34 | # https://flutter.dev/assets-and-images/#resolution-aware. 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/bart_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:bart/bart/bart_model.dart'; 3 | import 'package:bart/bart/bart_scaffold.dart'; 4 | import 'package:bart/bart/widgets/animated_appbar.dart'; 5 | import 'package:bart/bart/widgets/bottom_bar/bottom_bar.dart'; 6 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_material.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'components/custom_bottom_bar.dart'; 11 | import 'components/page_counter.dart'; 12 | import 'components/page_fake.dart'; 13 | 14 | void main() { 15 | group('Bart navigation with 3 items + subroutes', () { 16 | List homeSubRoutes() { 17 | return [ 18 | BartMenuRoute.bottomBar( 19 | label: "Home", 20 | icon: Icons.home, 21 | path: '/home', 22 | pageBuilder: (context, tabContext, settings) => PageFake( 23 | Colors.red, 24 | key: const ValueKey("page1"), 25 | child: Column( 26 | children: [ 27 | TextButton( 28 | key: const ValueKey("subpageBtn"), 29 | child: const Text( 30 | "Route to page 2", 31 | style: TextStyle(color: Colors.white), 32 | ), 33 | onPressed: () => 34 | Navigator.of(tabContext).pushNamed("/subpage"), 35 | ), 36 | TextButton( 37 | key: const ValueKey("goToLibraryButton"), 38 | child: const Text( 39 | "Go to library", 40 | style: TextStyle(color: Colors.white), 41 | ), 42 | onPressed: () => 43 | Navigator.of(tabContext).pushNamed("/library"), 44 | ), 45 | ], 46 | ), 47 | ), 48 | ), 49 | BartMenuRoute.bottomBar( 50 | label: "Library", 51 | icon: Icons.video_library_rounded, 52 | path: '/library', 53 | pageBuilder: (context, tabContext, settings) => PageFake( 54 | Colors.blueGrey, 55 | child: TextButton( 56 | key: const ValueKey("addAppBarBtn"), 57 | child: const Text( 58 | "add app bar", 59 | style: TextStyle(color: Colors.white), 60 | ), 61 | onPressed: () { 62 | Actions.invoke( 63 | tabContext, 64 | AppBarBuildIntent(AppBar( 65 | title: const Text("title text"), 66 | ))); 67 | Actions.invoke(tabContext, AppBarAnimationIntent.show()); 68 | }, 69 | ), 70 | ), 71 | ), 72 | BartMenuRoute.bottomBar( 73 | label: "Profile", 74 | icon: Icons.person, 75 | path: '/profile', 76 | pageBuilder: (context, tabContext, settings) => 77 | const PageFake(Colors.yellow), 78 | ), 79 | BartMenuRoute.innerRoute( 80 | path: '/subpage', 81 | pageBuilder: (context, tabContext, settings) => 82 | const PageFake(Colors.greenAccent, child: Text("Sub Route page")), 83 | ), 84 | ]; 85 | } 86 | 87 | createM3App({String? initialRoute}) { 88 | Route routes(RouteSettings settings) { 89 | switch (settings.name) { 90 | case '/': 91 | return MaterialPageRoute( 92 | settings: settings, 93 | builder: (context) => BartScaffold( 94 | routesBuilder: homeSubRoutes, 95 | initialRoute: initialRoute, 96 | bottomBar: BartBottomBar.material3(), 97 | ), 98 | maintainState: true, 99 | ); 100 | default: 101 | throw 'unexpected Route'; 102 | } 103 | } 104 | 105 | return MaterialApp( 106 | title: 'Flutter Demo', 107 | onGenerateRoute: routes, 108 | theme: ThemeData( 109 | primarySwatch: Colors.blue, 110 | visualDensity: VisualDensity.adaptivePlatformDensity, 111 | ), 112 | ); 113 | } 114 | 115 | createMaterialApp({String? initialRoute}) { 116 | Route routes(RouteSettings settings) { 117 | switch (settings.name) { 118 | case '/': 119 | return MaterialPageRoute( 120 | settings: settings, 121 | builder: (context) => BartScaffold( 122 | routesBuilder: homeSubRoutes, 123 | initialRoute: initialRoute, 124 | bottomBar: BartBottomBar.material(), 125 | ), 126 | maintainState: true, 127 | ); 128 | default: 129 | throw 'unexpected Route'; 130 | } 131 | } 132 | 133 | return MaterialApp( 134 | title: 'Flutter Demo', 135 | onGenerateRoute: routes, 136 | theme: ThemeData( 137 | primarySwatch: Colors.blue, 138 | visualDensity: VisualDensity.adaptivePlatformDensity, 139 | ), 140 | ); 141 | } 142 | 143 | createAppCupertino({String? initialRoute}) { 144 | Route routes(RouteSettings settings) { 145 | switch (settings.name) { 146 | case '/': 147 | return MaterialPageRoute( 148 | settings: settings, 149 | builder: (context) => BartScaffold( 150 | routesBuilder: homeSubRoutes, 151 | initialRoute: initialRoute, 152 | bottomBar: BartBottomBar.cupertino(), 153 | ), 154 | maintainState: true, 155 | ); 156 | default: 157 | throw 'unexpected Route'; 158 | } 159 | } 160 | 161 | return MaterialApp( 162 | title: 'Flutter Demo', 163 | onGenerateRoute: routes, 164 | theme: ThemeData( 165 | primarySwatch: Colors.blue, 166 | visualDensity: VisualDensity.adaptivePlatformDensity, 167 | ), 168 | ); 169 | } 170 | 171 | createAppCustom({String? initialRoute}) { 172 | Route routes(RouteSettings settings) { 173 | switch (settings.name) { 174 | case '/': 175 | return MaterialPageRoute( 176 | settings: settings, 177 | builder: (context) => BartScaffold( 178 | routesBuilder: homeSubRoutes, 179 | initialRoute: initialRoute, 180 | bottomBar: BartBottomBar.custom( 181 | bottomBarFactory: CustomBottomBar(), 182 | ), 183 | ), 184 | maintainState: true, 185 | ); 186 | default: 187 | throw 'unexpected Route'; 188 | } 189 | } 190 | 191 | return MaterialApp( 192 | title: 'Flutter Demo', 193 | onGenerateRoute: routes, 194 | theme: ThemeData( 195 | primarySwatch: Colors.blue, 196 | visualDensity: VisualDensity.adaptivePlatformDensity, 197 | ), 198 | ); 199 | } 200 | 201 | testWidgets('create app with bart bottom bar containing 3 tabs', 202 | (WidgetTester tester) async { 203 | await tester.pumpWidget(createMaterialApp(initialRoute: "/home")); 204 | await tester.pump(); 205 | expect(find.byType(BartScaffold), findsOneWidget); 206 | expect(find.byType(BottomNavigationBar), findsOneWidget); 207 | expect(find.byType(InkResponse), findsNWidgets(3)); 208 | }); 209 | testWidgets('page has no app bar, click on add appbar => an appbar exists', 210 | (WidgetTester tester) async { 211 | await tester.pumpWidget(createMaterialApp(initialRoute: "/library")); 212 | await tester.pump(); 213 | expect(find.byType(AppBar), findsNothing); 214 | var btnFinder = find.byKey(const ValueKey("addAppBarBtn")); 215 | expect(btnFinder, findsOneWidget); 216 | await tester.tap(btnFinder); 217 | // an app bar is visible 218 | await tester.pump(const Duration(seconds: 1)); 219 | expect(find.byType(AppBar), findsOneWidget); 220 | }); 221 | 222 | testWidgets('click on add appbar, route to next page => appBar is reset', 223 | (WidgetTester tester) async { 224 | await tester.pumpWidget(createMaterialApp(initialRoute: "/library")); 225 | await tester.pump(); 226 | var appBar = 227 | find.byType(AnimatedAppBar).evaluate().first.widget as AnimatedAppBar; 228 | var btnFinder = find.byKey(const ValueKey("addAppBarBtn")); 229 | expect(btnFinder, findsOneWidget); 230 | await tester.tap(btnFinder); 231 | // an app bar is visible 232 | await tester.pump(const Duration(seconds: 1)); 233 | expect(find.byType(AppBar), findsOneWidget); 234 | expect(appBar.showStateNotifier.value, isTrue); 235 | // route to second page, appbar is reset by defaultIcon 236 | var item2 = 237 | find.byType(InkResponse).at(2).evaluate().first.widget as InkResponse; 238 | item2.onTap!(); 239 | await tester.pump(const Duration(seconds: 1)); 240 | expect(appBar.showStateNotifier.value, isFalse); 241 | }); 242 | 243 | testWidgets('default tab is the first one', (WidgetTester tester) async { 244 | await tester.pumpWidget(createMaterialApp()); 245 | var currentPage = 246 | find.byType(PageFake).evaluate().first.widget as PageFake; 247 | expect(currentPage.bgColor, Colors.red); 248 | }); 249 | 250 | testWidgets('should create cupertino bottom bar', 251 | (WidgetTester tester) async { 252 | await tester.pumpWidget(createAppCupertino(initialRoute: "/library")); 253 | var currentPage = 254 | find.byType(PageFake).evaluate().first.widget as PageFake; 255 | expect(currentPage.bgColor, Colors.blueGrey); 256 | }); 257 | 258 | testWidgets('Create custom bottom bar with 3 tabs', 259 | (WidgetTester tester) async { 260 | await tester.pumpWidget(createAppCustom(initialRoute: "/home")); 261 | expect(find.byType(BartScaffold), findsOneWidget); 262 | expect(find.byKey(const ValueKey('CustomBottomBar')), findsOneWidget); 263 | expect(find.byKey(const ValueKey('BottomBarItem1')), findsOneWidget); 264 | expect(find.byKey(const ValueKey('BottomBarItem2')), findsOneWidget); 265 | expect(find.byKey(const ValueKey('BottomBarItem3')), findsOneWidget); 266 | expect(find.byKey(const ValueKey('BottomBarItem4')), findsNothing); 267 | // default tab should be the first one 268 | var currentPage = 269 | find.byType(PageFake).evaluate().first.widget as PageFake; 270 | expect(currentPage.bgColor, Colors.red); 271 | }); 272 | testWidgets('create app with material 3 bart bottom bar containing 3 tabs', 273 | (WidgetTester tester) async { 274 | await tester.pumpWidget(createM3App(initialRoute: "/home")); 275 | await tester.pump(); 276 | expect(find.byType(BartScaffold), findsOneWidget); 277 | expect(find.byType(NavigationBar), findsOneWidget); 278 | expect(find.byType(Icon), findsNWidgets(3)); 279 | }); 280 | 281 | testWidgets('bar is on tab 1, click on tab 2 => tab 2 page is visible', 282 | (WidgetTester tester) async { 283 | await tester.pumpWidget(createMaterialApp(initialRoute: "/home")); 284 | await tester.pump(); 285 | expect(find.byType(PageFake), findsNWidgets(1)); 286 | await tester.tap(find.byType(Icon).at(1)); 287 | await tester.pump(const Duration(seconds: 1)); 288 | var page1 = find.byType(PageFake).evaluate().last.widget as PageFake; 289 | expect(find.byType(PageFake), findsNWidgets(2)); 290 | expect(page1.bgColor, Colors.blueGrey); 291 | }); 292 | 293 | testWidgets( 294 | 'bar is on tab 1 (home), click on library page button 2 => tab 2 page is visible and tab 2 icon is selected', 295 | (WidgetTester tester) async { 296 | await tester.pumpWidget(createMaterialApp(initialRoute: "/home")); 297 | await tester.pump(); 298 | 299 | BartBottomBar bottomBar = 300 | tester.firstWidget(find.byType(BartBottomBar)) as BartBottomBar; 301 | expect(bottomBar.currentIndex, equals(0)); 302 | 303 | var libraryButton = find.byKey(const ValueKey('goToLibraryButton')); 304 | await tester.tap(libraryButton); 305 | await tester.pump(const Duration(seconds: 1)); 306 | expect(find.text('add app bar'), findsOneWidget); 307 | await tester.pumpAndSettle(); 308 | 309 | expect(find.byType(BartBottomBar), findsOneWidget); 310 | final materialBottomBar = 311 | tester.firstWidget(find.byType(BartMaterialBottomBar)) 312 | as BartMaterialBottomBar; 313 | expect(materialBottomBar.currentIndexNotifier.value, equals(1)); 314 | }); 315 | 316 | testWidgets( 317 | 'push a page => page is visible on top of tab, bottom navigation is still visible', 318 | (WidgetTester tester) async { 319 | await tester.pumpWidget(createMaterialApp(initialRoute: "/home")); 320 | await tester.pump(); 321 | var btnFinder = find.byKey(const ValueKey("subpageBtn")); 322 | expect(btnFinder, findsOneWidget); 323 | await tester.tap(btnFinder); 324 | // new page is visible 325 | await tester.pump(const Duration(seconds: 1)); 326 | expect(find.text("Sub Route page"), findsOneWidget); 327 | expect(find.byType(BartScaffold), findsOneWidget); 328 | expect(find.byType(BartMaterialBottomBar), findsOneWidget); 329 | expect(find.byType(Icon), findsNWidgets(3)); 330 | }); 331 | }); 332 | 333 | group('4 tabs, tab 1,3 are counters', () { 334 | List homeSubRoutes() { 335 | return [ 336 | BartMenuRoute.bottomBar( 337 | label: "home", 338 | icon: Icons.person, 339 | path: '/home', 340 | pageBuilder: (context, tabContext, settings) => 341 | PageFakeCounter(showAppBar: true), 342 | ), 343 | BartMenuRoute.bottomBar( 344 | label: "lib", 345 | icon: Icons.person, 346 | path: '/lib', 347 | pageBuilder: (context, tabContext, settings) => 348 | const PageFake(Colors.yellow), 349 | ), 350 | BartMenuRoute.bottomBar( 351 | label: "Profile", 352 | icon: Icons.person, 353 | path: '/profile', 354 | cache: false, 355 | pageBuilder: (context, tabContext, settings) => PageFakeCounter(), 356 | ), 357 | BartMenuRoute.innerRoute( 358 | path: '/subpage', 359 | pageBuilder: (context, tabContext, settings) => 360 | const PageFake(Colors.greenAccent, child: Text("Sub Route page")), 361 | ), 362 | ]; 363 | } 364 | 365 | createApp({String? initialRoute}) { 366 | Route routes(RouteSettings settings) { 367 | switch (settings.name) { 368 | case '/': 369 | return MaterialPageRoute( 370 | settings: settings, 371 | builder: (context) => BartScaffold( 372 | routesBuilder: homeSubRoutes, 373 | initialRoute: initialRoute, 374 | bottomBar: BartBottomBar.material(), 375 | ), 376 | maintainState: true, 377 | ); 378 | default: 379 | throw 'unexpected Route'; 380 | } 381 | } 382 | 383 | return MaterialApp( 384 | title: 'Flutter Demo', 385 | onGenerateRoute: routes, 386 | theme: ThemeData( 387 | primarySwatch: Colors.blue, 388 | visualDensity: VisualDensity.adaptivePlatformDensity, 389 | ), 390 | ); 391 | } 392 | 393 | testWidgets('''bar is on tab 1, shows appBar using mixin''', 394 | (WidgetTester tester) async { 395 | await tester.pumpWidget(createApp(initialRoute: "/home")); 396 | await tester.pump(); 397 | 398 | expect(find.byType(AppBar), findsOneWidget); 399 | }); 400 | 401 | testWidgets( 402 | '''bar is on tab 1 with cache=true, click on button increment counter (counter 1 => 2), go tab 2 then tab 1 403 | => tab 1 is back with restored state (counter = 2)''', 404 | (WidgetTester tester) async { 405 | await tester.pumpWidget(createApp(initialRoute: "/home")); 406 | await tester.pump(); 407 | // tap button to change counter 408 | expect(find.text("1"), findsOneWidget); 409 | await tester.tap(find.byKey(const ValueKey("addCountBtn"))); 410 | await tester.pump(); 411 | expect(find.text("2"), findsOneWidget); 412 | // go page 2 413 | var item1 = 414 | find.byType(InkResponse).at(1).evaluate().first.widget as InkResponse; 415 | item1.onTap!(); 416 | await tester.pump(const Duration(seconds: 1)); 417 | 418 | var page = find.byType(PageFake).evaluate().last.widget as PageFake; 419 | expect(page.bgColor, Colors.yellow); 420 | expect(find.byKey(const ValueKey("counter")), findsOneWidget); 421 | // go page 1 422 | var item0 = 423 | find.byType(InkResponse).at(0).evaluate().first.widget as InkResponse; 424 | item0.onTap!(); 425 | await tester.pump(const Duration(seconds: 1)); 426 | 427 | expect(find.byType(PageFakeCounter), findsOneWidget); 428 | expect(find.byKey(const ValueKey("counter")), findsOneWidget); 429 | var counter = 430 | find.byKey(const ValueKey("counter")).evaluate().first.widget as Text; 431 | expect(counter.data, "2"); 432 | }); 433 | 434 | testWidgets( 435 | '''bar is on tab 3 with cache=false, click on button increment counter (counter 1 => 2), go tab 2 then tab 3 436 | => tab 3 is back with initial state (counter = 1)''', 437 | (WidgetTester tester) async { 438 | await tester.pumpWidget(createApp(initialRoute: "/profile")); 439 | await tester.pump(); 440 | // tap button to change counter 441 | expect(find.text("1"), findsOneWidget); 442 | await tester.tap(find.byKey(const ValueKey("addCountBtn"))); 443 | await tester.pump(); 444 | expect(find.text("2"), findsOneWidget); 445 | // go page 2 446 | var item1 = 447 | find.byType(InkResponse).at(1).evaluate().first.widget as InkResponse; 448 | item1.onTap!(); 449 | await tester.pump(const Duration(seconds: 1)); 450 | 451 | var page = find.byType(PageFake).evaluate().last.widget as PageFake; 452 | expect(page.bgColor, Colors.yellow); 453 | expect(find.byKey(const ValueKey("counter")), findsOneWidget); 454 | // go page 1 455 | var item0 = 456 | find.byType(InkResponse).at(0).evaluate().first.widget as InkResponse; 457 | item0.onTap!(); 458 | await tester.pump(const Duration(seconds: 1)); 459 | 460 | expect(find.byType(PageFakeCounter), findsOneWidget); 461 | expect(find.byKey(const ValueKey("counter")), findsOneWidget); 462 | var counter = 463 | find.byKey(const ValueKey("counter")).evaluate().first.widget as Text; 464 | expect(counter.data, "1"); 465 | }); 466 | }); 467 | } 468 | -------------------------------------------------------------------------------- /test/components/custom_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart.dart'; 2 | import 'package:bart/bart/widgets/bottom_bar/styles/bottom_bar_custom.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class CustomBottomBar extends BartBottomBarFactory { 6 | @override 7 | Widget create({ 8 | required BuildContext context, 9 | required List routes, 10 | required void Function(int) onTap, 11 | required int currentIndex, 12 | }) { 13 | return Container( 14 | key: const ValueKey('CustomBottomBar'), 15 | height: 85, 16 | decoration: BoxDecoration( 17 | color: Colors.blue, 18 | boxShadow: [ 19 | BoxShadow( 20 | color: Colors.black.withOpacity(0.7), 21 | blurRadius: 3, 22 | ), 23 | ], 24 | ), 25 | child: SafeArea( 26 | child: Row( 27 | children: [ 28 | for (var i = 0; i < routes.length; i++) 29 | Expanded( 30 | child: GestureDetector( 31 | onTap: () => onTap(i), 32 | child: Column( 33 | key: ValueKey('BottomBarItem${i + 1}'), 34 | mainAxisAlignment: MainAxisAlignment.center, 35 | children: [ 36 | Icon( 37 | routes[i].icon, 38 | color: currentIndex == i ? Colors.white : Colors.black, 39 | ), 40 | const SizedBox(height: 4), 41 | Text( 42 | routes[i].label!, 43 | style: TextStyle( 44 | color: 45 | currentIndex == i ? Colors.white : Colors.black, 46 | ), 47 | ), 48 | ], 49 | ), 50 | ), 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/components/page_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:bart/bart/bart_appbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PageFakeCounter extends StatefulWidget { 5 | final ValueNotifier counter = ValueNotifier(1); 6 | final bool showAppBar; 7 | 8 | PageFakeCounter({super.key, this.showAppBar = false}); 9 | 10 | @override 11 | PageFakeCounterState createState() => PageFakeCounterState(); 12 | } 13 | 14 | class PageFakeCounterState extends State with AppBarNotifier { 15 | @override 16 | void initState() { 17 | super.initState(); 18 | 19 | if (widget.showAppBar) { 20 | updateAppBar(context, AppBar(title: const Text("appbar"))); 21 | showAppBar(context); 22 | } 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return ValueListenableBuilder( 28 | valueListenable: widget.counter, 29 | builder: (context, counter, child) => Container( 30 | color: Colors.white, 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | Text( 35 | "$counter", 36 | key: const ValueKey("counter"), 37 | ), 38 | TextButton( 39 | key: const ValueKey("addCountBtn"), 40 | onPressed: () { 41 | setState(() { 42 | widget.counter.value++; 43 | }); 44 | }, 45 | child: const Text("Add"), 46 | ), 47 | TextButton( 48 | key: const ValueKey("hideAppBar"), 49 | onPressed: () => hideAppBar(context), 50 | child: const Text("hide app bar"), 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/components/page_fake.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PageFake extends StatelessWidget { 4 | final Color bgColor; 5 | final Widget? child; 6 | 7 | const PageFake(this.bgColor, {this.child, super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | color: bgColor, 13 | child: Center(child: child), 14 | ); 15 | } 16 | } 17 | --------------------------------------------------------------------------------