From 0d67e67783b6d9ffc6f5b8f7ee2259ac575ff240 Mon Sep 17 00:00:00 2001 From: Minh Bao Date: Wed, 17 Apr 2024 00:19:58 +0700 Subject: [PATCH 1/6] First commit: add navbar_badge and add main example --- example/lib/main.dart | 94 ++++++++++++- example/pubspec.yaml | 1 + lib/navbar_router.dart | 1 + lib/src/animated_navbar.dart | 128 ++++++++++++----- lib/src/navbar_badge.dart | 244 +++++++++++++++++++++++++++++++++ lib/src/navbar_decoration.dart | 11 +- lib/src/navbar_notifier.dart | 38 ++++- lib/src/navbar_router.dart | 12 +- pubspec.yaml | 1 + 9 files changed, 486 insertions(+), 44 deletions(-) create mode 100644 lib/src/navbar_badge.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index d602cec..8cf129a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:math' hide log; +import 'package:badges/badges.dart'; import 'package:example/app_controller.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -106,19 +108,30 @@ class _HomePageState extends ConsumerState { selectedIcon: const Icon( Icons.home, size: 26, + ), + badge: NavbarBadge( + badgeText: "11", + showBadge: true, )), NavbarItem(Icons.shopping_bag_outlined, 'Products', backgroundColor: colors[1], selectedIcon: const Icon( Icons.shopping_bag, size: 26, + ), + badge: NavbarBadge( + badgeText: "8", + showBadge: true, )), NavbarItem(Icons.person_outline, 'Me', backgroundColor: colors[2], selectedIcon: const Icon( Icons.person, size: 26, - )), + ), + // dot badge + badge: + NavbarBadge(badgeText: "", showBadge: true, color: Colors.amber)), NavbarItem(Icons.settings_outlined, 'Settings', backgroundColor: colors[0], selectedIcon: const Icon( @@ -755,11 +768,88 @@ class _SettingsState extends State { index = x; }); appSetting.changeThemeSeed(themeColorSeed[index.toInt()]); - }) + }), + const SizedBox( + height: 40, + ), + ElevatedButton( + onPressed: () { + showRandomBadges(); + }, + child: const Text('Show Random Badges')) ], ), )); } + + showRandomBadges() { + var anims = [ + BadgeAnimation.fade, + BadgeAnimation.rotation, + BadgeAnimation.scale, + BadgeAnimation.size, + BadgeAnimation.slide + ]; + var pos = [ + BadgePosition.bottomEnd, + BadgePosition.bottomStart, + BadgePosition.topEnd, + BadgePosition.topStart + ]; + int r = Random().nextInt(100); + var b = Random().nextBool(); + NavbarNotifier.setBadges( + 0, + NavbarBadge( + badgeText: "${b ? r : ""}", + color: b + ? null + : themeColorSeed[Random().nextInt(themeColorSeed.length)], + position: pos[Random().nextInt(pos.length)](), + showBadge: Random().nextInt(5) > 1, + badgeAnimation: anims[Random().nextInt(anims.length)](), + animationDuration: + Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); + b = Random().nextBool(); + NavbarNotifier.setBadges( + 1, + NavbarBadge( + badgeText: "${b ? r : ""}", + position: pos[Random().nextInt(pos.length)](), + color: b + ? null + : themeColorSeed[Random().nextInt(themeColorSeed.length)], + showBadge: Random().nextInt(5) > 1, + badgeAnimation: anims[Random().nextInt(anims.length)](), + animationDuration: + Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); + b = Random().nextBool(); + NavbarNotifier.setBadges( + 2, + NavbarBadge( + badgeText: "${b ? r : ""}", + position: pos[Random().nextInt(pos.length)](), + color: b + ? null + : themeColorSeed[Random().nextInt(themeColorSeed.length)], + showBadge: Random().nextInt(5) > 1, + badgeAnimation: anims[Random().nextInt(anims.length)](), + animationDuration: + Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); + b = Random().nextBool(); + NavbarNotifier.setBadges( + 3, + NavbarBadge( + badgeText: "${b ? r : ""}", + position: pos[Random().nextInt(pos.length)](), + color: b + ? null + : themeColorSeed[Random().nextInt(themeColorSeed.length)], + showBadge: Random().nextInt(5) > 1, + badgeAnimation: anims[Random().nextInt(anims.length)](), + animationDuration: + Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); + } } class ProfileEdit extends StatelessWidget { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3b1fde5..1fc6337 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: path: ../ flutter: sdk: flutter + badges: cupertino_icons: ^1.0.2 flutter_riverpod: ^2.4.9 diff --git a/lib/navbar_router.dart b/lib/navbar_router.dart index 6389c04..427f23a 100644 --- a/lib/navbar_router.dart +++ b/lib/navbar_router.dart @@ -4,3 +4,4 @@ export 'src/navbar_router.dart'; export 'src/navbar_notifier.dart'; export 'src/navbar_decoration.dart'; export 'src/navigate.dart'; +export 'src/navbar_badge.dart'; diff --git a/lib/src/animated_navbar.dart b/lib/src/animated_navbar.dart index 825a750..206c80a 100644 --- a/lib/src/animated_navbar.dart +++ b/lib/src/animated_navbar.dart @@ -10,6 +10,44 @@ const double kFloatingNavbarHeight = 60.0; /// The height of the navbar based on the [NavbarType] double kNavbarHeight = 0.0; +/// Function to build badges, using index and child from the [NavbarNotifier.badges] list (given by user) +Widget buildBadge( + /// Current index of the navbar + int index, + + /// The navbar icon + Widget child, +) { + return badges.Badge( + key: NavbarNotifier.badges[index].key, + position: NavbarNotifier.badges[index].position ?? + (NavbarNotifier.badges[index].badgeText.isNotEmpty + ? badges.BadgePosition.topEnd(top: -15, end: -15) + : badges.BadgePosition.topEnd()), + badgeAnimation: NavbarNotifier.badges[index].badgeAnimation ?? + const badges.BadgeAnimation.slide( + // disappearanceFadeAnimationDuration: Duration(milliseconds: 200), + // curve: Curves.easeInCubic, + ), + ignorePointer: NavbarNotifier.badges[index].ignorePointer, + stackFit: NavbarNotifier.badges[index].stackFit, + onTap: NavbarNotifier.badges[index].onTap, + showBadge: NavbarNotifier.badges[index].showBadge, + badgeStyle: badges.BadgeStyle( + badgeColor: NavbarNotifier.badges[index].color ?? Colors.white, + ), + badgeContent: NavbarNotifier.badges[index].badgeContent ?? + Text( + NavbarNotifier.badges[index].badgeText, + style: NavbarNotifier.badges[index].badgeTextStyle ?? + TextStyle( + color: NavbarNotifier.badges[index].textColor ?? Colors.black, + fontSize: 9), + ), + child: child, + ); +} + class _AnimatedNavBar extends StatefulWidget { const _AnimatedNavBar( {Key? key, @@ -335,12 +373,13 @@ class _AnimatedNavBarState extends State<_AnimatedNavBar> extended: navigationRailDefaultDecoration.isExtended, backgroundColor: navigationRailDefaultDecoration.backgroundColor ?? theme.colorScheme.surface, - destinations: widget.menuItems.map((NavbarItem menuItem) { - return NavigationRailDestination( - icon: Icon(menuItem.iconData), - label: Text(menuItem.text), - ); - }).toList(), + destinations: [ + for (int i = 0; i < widget.menuItems.length; i++) + NavigationRailDestination( + icon: buildBadge(i, Icon(widget.menuItems[i].iconData)), + label: Text(widget.menuItems[i].text), + ) + ], selectedIndex: NavbarNotifier.currentIndex); } @@ -441,12 +480,13 @@ class StandardNavbarState extends State { BottomNavigationBarItem( backgroundColor: items[index].backgroundColor, icon: _selectedIndex == index - ? items[index].selectedIcon ?? - Icon( - items[index].iconData, - ) - : Icon( - items[index].iconData, + ? buildBadge( + index, + items[index].selectedIcon ?? Icon(items[index].iconData), + ) + : buildBadge( + index, + Icon(items[index].iconData), ), label: items[index].text, ) @@ -592,13 +632,15 @@ class NotchedNavBarState extends State ), ], ), - child: widget.menuItems[NavbarNotifier.currentIndex].selectedIcon ?? - Icon( - widget.menuItems[NavbarNotifier.currentIndex].iconData, - color: widget.decoration.selectedIconColor, - size: (widget.decoration.selectedIconTheme?.size ?? 24.0) * - scaleAnimation.value, - )); + child: buildBadge( + NavbarNotifier.currentIndex, + widget.menuItems[NavbarNotifier.currentIndex].selectedIcon ?? + Icon( + widget.menuItems[NavbarNotifier.currentIndex].iconData, + color: widget.decoration.selectedIconColor, + size: (widget.decoration.selectedIconTheme?.size ?? 24.0) * + scaleAnimation.value, + ))); } @override @@ -651,6 +693,7 @@ class NotchedNavBarState extends State alignment: Alignment.center, height: 80, child: MenuTile( + index: i, item: widget.menuItems[i], decoration: widget.decoration, ), @@ -667,8 +710,13 @@ class NotchedNavBarState extends State class MenuTile extends StatelessWidget { final NavbarDecoration decoration; final NavbarItem item; + final int index; - const MenuTile({super.key, required this.item, required this.decoration}); + const MenuTile( + {super.key, + required this.item, + required this.decoration, + required this.index}); @override Widget build(BuildContext context) { @@ -676,11 +724,13 @@ class MenuTile extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - item.iconData, - color: - decoration.unselectedIconColor ?? decoration.unselectedItemColor, - ), + buildBadge( + index, + Icon( + item.iconData, + color: decoration.unselectedIconColor ?? + decoration.unselectedItemColor, + )), const SizedBox( height: 6, ), @@ -836,14 +886,18 @@ class M3NavBarState extends State { indicatorColor: widget.decoration.indicatorColor, indicatorShape: widget.decoration.indicatorShape, labelBehavior: widget.labelBehavior, - destinations: widget.items.map((e) { - return NavigationDestination( - tooltip: e.text, - icon: Icon(e.iconData), - label: e.text, - selectedIcon: e.selectedIcon ?? Icon(e.iconData), - ); - }).toList(), + destinations: [ + for (int i = 0; i < widget.items.length; i++) + NavigationDestination( + tooltip: widget.items[i].text, + icon: buildBadge(i, Icon(widget.items[i].iconData)), + label: widget.items[i].text, + selectedIcon: buildBadge( + i, + widget.items[i].selectedIcon ?? + Icon(widget.items[i].iconData)), + ) + ], selectedIndex: NavbarNotifier.currentIndex, onDestinationSelected: (int index) => widget.onItemTapped!(index)), ), @@ -985,9 +1039,11 @@ class FloatingNavbarState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ _selectedIndex == i - ? widget.items[i].selectedIcon ?? - _unselectedIcon(i) - : _unselectedIcon(i), + ? buildBadge( + i, + widget.items[i].selectedIcon ?? + _unselectedIcon(i)) + : buildBadge(i, _unselectedIcon(i)), if (widget.decoration.showSelectedLabels! && widget.index == i) Text( diff --git a/lib/src/navbar_badge.dart b/lib/src/navbar_badge.dart new file mode 100644 index 0000000..fa3e0ae --- /dev/null +++ b/lib/src/navbar_badge.dart @@ -0,0 +1,244 @@ +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; + +class NavbarBadge { + /// Your badge content, can be number (as string) of text. + /// Please choose either [badgeText] or [badgeContent]. + /// + /// If **[badgeContent]** is not null, **[badgeText]** will be **ignored**. + String badgeText; + + /// Text style for badge + TextStyle? badgeTextStyle; + + /// Content inside badge. Please choose either [badgeText] or [badgeContent]. + /// + /// If not null, **[badgeText]** will be **ignored**. + Widget? badgeContent; + + /// Allows you to hide or show entire badge. + /// The default value is false. + bool showBadge; + + /// Duration of the badge animations when the [badgeContent] changes. + /// The default value is Duration(milliseconds: 500). + Duration animationDuration; + + /// Background color of the badge. + /// The default value is white. + Color? color; + + /// Text color of the badge. + /// The default value is black. + Color? textColor; + + /// Contains all badge style properties. + /// + /// Allows to set the shape to this [badgeContent]. + /// The default value is [BadgeShape.circle]. + /// ``` + /// final BadgeShape shape; + /// ``` + /// Allows to set border radius to this [badgeContent]. + /// The default value is [BorderRadius.zero]. + /// ``` + /// final BorderRadius borderRadius; + /// ``` + /// Background color of the badge. + /// If [gradient] is not null, this property will be ignored. + /// ``` + /// final Color badgeColor; + /// ``` + /// Allows to set border side to this [badgeContent]. + /// The default value is [BorderSide.none]. + /// ``` + /// final BorderSide borderSide; + /// ``` + /// The size of the shadow below the badge. + /// ``` + /// final double elevation; + /// ``` + /// Background gradient color of the badge. + /// Will be used over [badgeColor] if not null. + /// ``` + /// final BadgeGradient? badgeGradient; + /// ``` + /// Background gradient color of the border badge. + /// Will be used over [borderSide.color] if not null. + /// ``` + /// final BadgeGradient? borderGradient; + /// ``` + /// Specifies padding for [badgeContent]. + /// The default value is EdgeInsets.all(5.0). + /// ``` + /// final EdgeInsetsGeometry padding; + /// ``` + BadgeStyle badgeStyle; + + /// Contains all badge animation properties. + /// + /// True to animate badge on [badgeContent] change. + /// False to disable animation. + /// Default value is true. + /// ``` + /// final bool toAnimate; + /// ``` + /// Duration of the badge animations when the [badgeContent] changes. + /// The default value is Duration(milliseconds: 500). + /// ``` + /// final Duration animationDuration; + /// ``` + /// Duration of the badge appearance and disappearance fade animations. + /// Fade animation is created with [AnimatedOpacity]. + /// + /// Some of the [BadgeAnimationType] cannot be used for appearance and disappearance animation. + /// E.g. [BadgeAnimationType.scale] can be used, but [BadgeAnimationType.rotation] cannot be used. + /// That is why we need fade animation and duration for it when it comes to appearance and disappearance + /// of these "non-disappearing" animations. + /// + /// There is a thing: you need this duration to NOT be longer than [animationDuration] + /// if you want to use the basic animation as appearance and disappearance animation. + /// + /// Set this to zero to skip the badge appearance and disappearance animations + /// The default value is Duration(milliseconds: 200). + /// ``` + /// final Duration disappearanceFadeAnimationDuration; + /// ``` + /// Type of the animation for badge + /// The default value is [BadgeAnimationType.slide]. + /// ``` + /// final BadgeAnimationType animationType; + /// ``` + /// Make it true to have infinite animation + /// False to have animation only when [badgeContent] is changed + /// The default value is false + /// ``` + /// final bool loopAnimation; + /// ``` + /// Controls curve of the animation + /// ``` + /// final Curve curve; + /// ``` + /// Used only for [SizeTransition] animation + /// The default value is Axis.horizontal + /// ``` + /// final Axis? sizeTransitionAxis; + /// ``` + /// Used only for [SizeTransition] animation + /// The default value is 1.0 + /// ``` + /// final double? sizeTransitionAxisAlignment; + /// ``` + /// Used only for [SlideTransition] animation + /// The default value is + /// ``` + /// SlideTween( + /// begin: const Offset(-0.5, 0.9), + /// end: const Offset(0.0, 0.0), + /// ); + /// ``` + /// ``` + /// final SlideTween? slideTransitionPositionTween; + /// ``` + /// Used only for changing color animation. + /// The default value is [Curves.linear] + /// ``` + /// final Curve colorChangeAnimationCurve; + /// ``` + /// Used only for changing color animation. + /// The default value is [Duration.zero], meaning that + /// no animation will be applied to color change by default. + /// ``` + /// final Duration colorChangeAnimationDuration; + /// ``` + /// This one is interesting. + /// Some animations use [AnimatedOpacity] to animate appearance and disappearance of the badge. + /// E.x. how would you animate disappearance of [BadgeAnimationType.rotation]? We should use [AnimatedOpacity] for that. + /// But sometimes you may need to disable this fade appearance/disappearance animation. + /// You can do that by setting this to false. + /// Using disappearanceFadeAnimationDuration: Duration.zero is not correct, this will remove the animation entirely + /// ``` + /// final bool appearanceDisappearanceFadeAnimationEnabled; + /// ``` + BadgeAnimation? badgeAnimation; + + /// Allows to set custom position of badge according to [child]. + /// If [child] is null, it doesn't make sense to use it. + BadgePosition? position; + + /// Can make your [badgeContent] interactive. + /// The default value is false. + /// Make it true to make badge intercept all taps + /// Make it false and all taps will be passed through the badge + bool ignorePointer; + + /// Allows to edit fit parameter to [Stack] widget. + /// The default value is [StackFit.loose]. + StackFit stackFit; + + /// Will be called when you tap on the badge + /// Important: if the badge is outside of the child + /// the additional padding will be applied to make the full badge clickable + Function()? onTap; + + Key? key; + + NavbarBadge({ + this.key, + this.badgeText = "", + this.showBadge = false, + this.animationDuration = const Duration(milliseconds: 500), + this.color, + this.textColor, + this.badgeStyle = const BadgeStyle(), + this.badgeAnimation = const BadgeAnimation.slide(), + this.position, + this.ignorePointer = false, + this.stackFit = StackFit.loose, + this.onTap, + this.badgeContent, + this.badgeTextStyle, + }); + + /// Clear the content of the badge and hide it. + void clearBadge() { + badgeText = ""; + showBadge = false; + } + + @override + int get hashCode => + badgeText.hashCode ^ + showBadge.hashCode ^ + animationDuration.hashCode ^ + color.hashCode ^ + textColor.hashCode ^ + badgeStyle.hashCode ^ + badgeAnimation.hashCode ^ + position.hashCode ^ + ignorePointer.hashCode ^ + stackFit.hashCode ^ + onTap.hashCode ^ + badgeContent.hashCode ^ + badgeStyle.hashCode; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is NavbarBadge && + runtimeType == other.runtimeType && + badgeText == other.badgeText && + showBadge == other.showBadge && + animationDuration == other.animationDuration && + color == other.color && + textColor == other.textColor && + badgeStyle == other.badgeStyle && + badgeAnimation == other.badgeAnimation && + position == other.position && + ignorePointer == other.ignorePointer && + stackFit == other.stackFit && + onTap == other.onTap && + badgeContent == other.badgeContent && + badgeStyle == other.badgeStyle; + } +} diff --git a/lib/src/navbar_decoration.dart b/lib/src/navbar_decoration.dart index 2bfb49f..1b7a9d8 100644 --- a/lib/src/navbar_decoration.dart +++ b/lib/src/navbar_decoration.dart @@ -3,7 +3,7 @@ import 'package:navbar_router/navbar_router.dart'; class NavbarItem { const NavbarItem(this.iconData, this.text, - {this.backgroundColor, this.child, this.selectedIcon}); + {this.backgroundColor, this.child, this.selectedIcon, this.badge}); /// IconData for the navbar item final IconData iconData; @@ -22,6 +22,9 @@ class NavbarItem { /// Widget to show when the item is selected final Widget? selectedIcon; + /// Your initial badge configuration for this item, this is totally optional + final NavbarBadge? badge; + @override bool operator ==(Object other) => identical(this, other) || @@ -31,7 +34,8 @@ class NavbarItem { text == other.text && child.runtimeType == other.child.runtimeType && selectedIcon.runtimeType == other.selectedIcon.runtimeType && - backgroundColor == other.backgroundColor; + backgroundColor == other.backgroundColor && + badge == other.badge; @override int get hashCode => @@ -39,7 +43,8 @@ class NavbarItem { text.hashCode ^ child.hashCode ^ selectedIcon.hashCode ^ - backgroundColor.hashCode; + backgroundColor.hashCode ^ + badge.hashCode; } /// Decoration class for the navbar [NavbarType.standard] diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index c58a2b4..82c959b 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:navbar_router/src/navbar_router.dart'; +import 'package:navbar_router/navbar_router.dart'; class NavbarNotifier extends ChangeNotifier { static final NavbarNotifier _singleton = NavbarNotifier._internal(); @@ -30,8 +30,40 @@ class NavbarNotifier extends ChangeNotifier { static List> _keys = []; - static void setKeys(List> value) { + /// Set to true will hide the badges when the tap on the navbar icon. + static bool hideBadgeOnPageChanged = true; + + /// List of badges of the navbar + static List get badges => _badges; + static List _badges = []; + + /// Use to update a badge using its [index], e.g: update the number, text... + /// + /// If you want to hide badges on a specific index, use [makeBadgeVisible] + /// + static void setBadges(int index, NavbarBadge badge) { + if (index < 0 || index >= length) return; + badges[index] = badge; + // TODO: wonder if this will cause performance issue + _notifyIndexChangeListeners(index); + _singleton.notify(); + } + + /// Use to set the visibility of a badge using its [index]. + static void makeBadgeVisible(int index, bool visible) { + if (index < 0 || index >= length) return; + badges[index].showBadge = visible; + } + + /// Conveniently setup the badges if user choose to show them. Also the only place that init the badges. + /// + /// Will throw AssertionError if length of keys and given badgeList are not the same + static void setKeys(List> value, + {List? badgeList}) { _keys = value; + if (badgeList != null) { + _badges = badgeList; + } } static final List _indexChangeListeners = []; @@ -40,6 +72,7 @@ class NavbarNotifier extends ChangeNotifier { static set index(int x) { _index = x; + if (hideBadgeOnPageChanged) badges[x].clearBadge(); if (_navbarStackHistory.contains(x)) { _navbarStackHistory.remove(x); } @@ -242,5 +275,6 @@ class NavbarNotifier extends ChangeNotifier { _keys.clear(); _index = null; _length = null; + _badges.clear(); } } diff --git a/lib/src/navbar_router.dart b/lib/src/navbar_router.dart index f9e9ba3..18780c7 100644 --- a/lib/src/navbar_router.dart +++ b/lib/src/navbar_router.dart @@ -1,3 +1,4 @@ +import 'package:badges/badges.dart' as badges; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:navbar_router/navbar_router.dart'; @@ -148,6 +149,9 @@ class NavbarRouter extends StatefulWidget { /// defaults to the first item in the list of [NavbarItems] final int initialIndex; + /// Set to true will hide the badges when the tap on the navbar icon. + final bool hideBadgeOnPageChanged; + /// Take a look at the [readme](https://github.com/maheshmnj/navbar_router) for more information on how to use this package. /// /// Please help me improve this package. @@ -170,6 +174,7 @@ class NavbarRouter extends StatefulWidget { this.destinationAnimationDuration = 300, this.backButtonBehavior = BackButtonBehavior.exit, this.onCurrentTabClicked, + this.hideBadgeOnPageChanged = true, this.onBackButtonPressed}) : assert(destinations.length >= 2, "Destinations length must be greater than or equal to 2"), @@ -193,12 +198,17 @@ class _NavbarRouterState extends State void initialize({bool isUpdate = false}) { NavbarNotifier.length = widget.destinations.length; + // init badge + List badges = []; for (int i = 0; i < NavbarNotifier.length; i++) { final navbaritem = widget.destinations[i].navbarItem; keys.add(GlobalKey()); items.add(navbaritem); + badges.add(navbaritem.badge ?? NavbarBadge()); } - NavbarNotifier.setKeys(keys); + // set badge list here + NavbarNotifier.setKeys(keys, badgeList: badges); + NavbarNotifier.hideBadgeOnPageChanged = widget.hideBadgeOnPageChanged; if (!isUpdate) { initAnimation(); NavbarNotifier.index = widget.initialIndex; diff --git a/pubspec.yaml b/pubspec.yaml index 1170c18..553f35a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ environment: dependencies: flutter: sdk: flutter + badges: ^3.1.2 dev_dependencies: flutter_test: From 96dd927ba955d215683f364a25f832b35190c36d Mon Sep 17 00:00:00 2001 From: Minh Bao Date: Wed, 17 Apr 2024 01:01:41 +0700 Subject: [PATCH 2/6] fix: tap on the same item to hide badge + update comments --- lib/src/navbar_badge.dart | 5 ++++- lib/src/navbar_notifier.dart | 2 ++ lib/src/navbar_router.dart | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/src/navbar_badge.dart b/lib/src/navbar_badge.dart index fa3e0ae..fcab670 100644 --- a/lib/src/navbar_badge.dart +++ b/lib/src/navbar_badge.dart @@ -1,6 +1,8 @@ import 'package:badges/badges.dart'; import 'package:flutter/material.dart'; +/// A customized badge/dot class for NavbarRouter. +/// Based on [badges] package of flutter. class NavbarBadge { /// Your badge content, can be number (as string) of text. /// Please choose either [badgeText] or [badgeContent]. @@ -68,7 +70,7 @@ class NavbarBadge { /// ``` /// final BadgeGradient? borderGradient; /// ``` - /// Specifies padding for [badgeContent]. + /// Specifies padding/**size** for [badgeContent]. /// The default value is EdgeInsets.all(5.0). /// ``` /// final EdgeInsetsGeometry padding; @@ -183,6 +185,7 @@ class NavbarBadge { Key? key; + /// Use padding of [badgeStyle] or fontSize of [badgeTextStyle] to change size of the badge/dot. NavbarBadge({ this.key, this.badgeText = "", diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index 82c959b..8131869 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -53,6 +53,8 @@ class NavbarNotifier extends ChangeNotifier { static void makeBadgeVisible(int index, bool visible) { if (index < 0 || index >= length) return; badges[index].showBadge = visible; + _notifyIndexChangeListeners(index); + _singleton.notify(); } /// Conveniently setup the badges if user choose to show them. Also the only place that init the badges. diff --git a/lib/src/navbar_router.dart b/lib/src/navbar_router.dart index 18780c7..37c8798 100644 --- a/lib/src/navbar_router.dart +++ b/lib/src/navbar_router.dart @@ -364,6 +364,10 @@ class _NavbarRouterState extends State if (widget.onCurrentTabClicked != null) { widget.onCurrentTabClicked!(); } + if (widget.hideBadgeOnPageChanged) { + NavbarNotifier.makeBadgeVisible( + NavbarNotifier.currentIndex, false); + } } else { NavbarNotifier.index = x; if (widget.onChanged != null) { From dd7eebb057c2e2f7d117ac692ed950c77a900ba9 Mon Sep 17 00:00:00 2001 From: Minh Bao Date: Wed, 17 Apr 2024 12:48:02 +0700 Subject: [PATCH 3/6] added approproate tests for badges + make the badge class const --- example/lib/main.dart | 16 +-- lib/src/animated_navbar.dart | 9 +- lib/src/navbar_badge.dart | 72 ++++++++---- lib/src/navbar_notifier.dart | 8 +- lib/src/navbar_router.dart | 4 +- test/navbar_router_test.dart | 207 ++++++++++++++++++++++++++++++++++- 6 files changed, 276 insertions(+), 40 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 8cf129a..4062890 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -109,7 +109,7 @@ class _HomePageState extends ConsumerState { Icons.home, size: 26, ), - badge: NavbarBadge( + badge: const NavbarBadge( badgeText: "11", showBadge: true, )), @@ -119,7 +119,7 @@ class _HomePageState extends ConsumerState { Icons.shopping_bag, size: 26, ), - badge: NavbarBadge( + badge: const NavbarBadge( badgeText: "8", showBadge: true, )), @@ -130,8 +130,8 @@ class _HomePageState extends ConsumerState { size: 26, ), // dot badge - badge: - NavbarBadge(badgeText: "", showBadge: true, color: Colors.amber)), + badge: const NavbarBadge( + badgeText: "", showBadge: true, color: Colors.amber)), NavbarItem(Icons.settings_outlined, 'Settings', backgroundColor: colors[0], selectedIcon: const Icon( @@ -798,7 +798,7 @@ class _SettingsState extends State { ]; int r = Random().nextInt(100); var b = Random().nextBool(); - NavbarNotifier.setBadges( + NavbarNotifier.updateBadge( 0, NavbarBadge( badgeText: "${b ? r : ""}", @@ -811,7 +811,7 @@ class _SettingsState extends State { animationDuration: Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); b = Random().nextBool(); - NavbarNotifier.setBadges( + NavbarNotifier.updateBadge( 1, NavbarBadge( badgeText: "${b ? r : ""}", @@ -824,7 +824,7 @@ class _SettingsState extends State { animationDuration: Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); b = Random().nextBool(); - NavbarNotifier.setBadges( + NavbarNotifier.updateBadge( 2, NavbarBadge( badgeText: "${b ? r : ""}", @@ -837,7 +837,7 @@ class _SettingsState extends State { animationDuration: Duration(milliseconds: (Random().nextInt(5) + 5) * 600))); b = Random().nextBool(); - NavbarNotifier.setBadges( + NavbarNotifier.updateBadge( 3, NavbarBadge( badgeText: "${b ? r : ""}", diff --git a/lib/src/animated_navbar.dart b/lib/src/animated_navbar.dart index 206c80a..f245886 100644 --- a/lib/src/animated_navbar.dart +++ b/lib/src/animated_navbar.dart @@ -25,10 +25,11 @@ Widget buildBadge( ? badges.BadgePosition.topEnd(top: -15, end: -15) : badges.BadgePosition.topEnd()), badgeAnimation: NavbarNotifier.badges[index].badgeAnimation ?? - const badges.BadgeAnimation.slide( - // disappearanceFadeAnimationDuration: Duration(milliseconds: 200), - // curve: Curves.easeInCubic, - ), + badges.BadgeAnimation.slide( + animationDuration: NavbarNotifier.badges[index].animationDuration, + // disappearanceFadeAnimationDuration: Duration(milliseconds: 200), + // curve: Curves.easeInCubic, + ), ignorePointer: NavbarNotifier.badges[index].ignorePointer, stackFit: NavbarNotifier.badges[index].stackFit, onTap: NavbarNotifier.badges[index].onTap, diff --git a/lib/src/navbar_badge.dart b/lib/src/navbar_badge.dart index fcab670..6759946 100644 --- a/lib/src/navbar_badge.dart +++ b/lib/src/navbar_badge.dart @@ -8,31 +8,31 @@ class NavbarBadge { /// Please choose either [badgeText] or [badgeContent]. /// /// If **[badgeContent]** is not null, **[badgeText]** will be **ignored**. - String badgeText; + final String badgeText; /// Text style for badge - TextStyle? badgeTextStyle; + final TextStyle? badgeTextStyle; /// Content inside badge. Please choose either [badgeText] or [badgeContent]. /// /// If not null, **[badgeText]** will be **ignored**. - Widget? badgeContent; + final Widget? badgeContent; /// Allows you to hide or show entire badge. /// The default value is false. - bool showBadge; + final bool showBadge; /// Duration of the badge animations when the [badgeContent] changes. /// The default value is Duration(milliseconds: 500). - Duration animationDuration; + final Duration animationDuration; /// Background color of the badge. /// The default value is white. - Color? color; + final Color? color; /// Text color of the badge. /// The default value is black. - Color? textColor; + final Color? textColor; /// Contains all badge style properties. /// @@ -75,7 +75,7 @@ class NavbarBadge { /// ``` /// final EdgeInsetsGeometry padding; /// ``` - BadgeStyle badgeStyle; + final BadgeStyle badgeStyle; /// Contains all badge animation properties. /// @@ -162,36 +162,36 @@ class NavbarBadge { /// ``` /// final bool appearanceDisappearanceFadeAnimationEnabled; /// ``` - BadgeAnimation? badgeAnimation; + final BadgeAnimation? badgeAnimation; /// Allows to set custom position of badge according to [child]. /// If [child] is null, it doesn't make sense to use it. - BadgePosition? position; + final BadgePosition? position; /// Can make your [badgeContent] interactive. /// The default value is false. /// Make it true to make badge intercept all taps /// Make it false and all taps will be passed through the badge - bool ignorePointer; + final bool ignorePointer; /// Allows to edit fit parameter to [Stack] widget. /// The default value is [StackFit.loose]. - StackFit stackFit; + final StackFit stackFit; /// Will be called when you tap on the badge /// Important: if the badge is outside of the child /// the additional padding will be applied to make the full badge clickable - Function()? onTap; + final Function()? onTap; - Key? key; + final Key? key; /// Use padding of [badgeStyle] or fontSize of [badgeTextStyle] to change size of the badge/dot. - NavbarBadge({ + const NavbarBadge({ this.key, this.badgeText = "", this.showBadge = false, this.animationDuration = const Duration(milliseconds: 500), - this.color, + this.color = Colors.white, this.textColor, this.badgeStyle = const BadgeStyle(), this.badgeAnimation = const BadgeAnimation.slide(), @@ -203,12 +203,6 @@ class NavbarBadge { this.badgeTextStyle, }); - /// Clear the content of the badge and hide it. - void clearBadge() { - badgeText = ""; - showBadge = false; - } - @override int get hashCode => badgeText.hashCode ^ @@ -244,4 +238,38 @@ class NavbarBadge { badgeContent == other.badgeContent && badgeStyle == other.badgeStyle; } + + NavbarBadge copyWith({ + String? badgeText, + TextStyle? badgeTextStyle, + Widget? badgeContent, + bool? showBadge, + Duration? animationDuration, + Color? color, + Color? textColor, + BadgeStyle? badgeStyle, + BadgeAnimation? badgeAnimation, + BadgePosition? position, + bool? ignorePointer, + StackFit? stackFit, + Function()? onTap, + Key? key, + }) { + return NavbarBadge( + badgeText: badgeText ?? this.badgeText, + badgeTextStyle: badgeTextStyle ?? this.badgeTextStyle, + badgeContent: badgeContent ?? this.badgeContent, + showBadge: showBadge ?? this.showBadge, + animationDuration: animationDuration ?? this.animationDuration, + color: color ?? this.color, + textColor: textColor ?? this.textColor, + badgeStyle: badgeStyle ?? this.badgeStyle, + badgeAnimation: badgeAnimation ?? this.badgeAnimation, + position: position ?? this.position, + ignorePointer: ignorePointer ?? this.ignorePointer, + stackFit: stackFit ?? this.stackFit, + onTap: onTap ?? this.onTap, + key: key ?? this.key, + ); + } } diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index 8131869..b296759 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -41,9 +41,9 @@ class NavbarNotifier extends ChangeNotifier { /// /// If you want to hide badges on a specific index, use [makeBadgeVisible] /// - static void setBadges(int index, NavbarBadge badge) { + static void updateBadge(int index, NavbarBadge badge) { if (index < 0 || index >= length) return; - badges[index] = badge; + _badges[index] = badge; // TODO: wonder if this will cause performance issue _notifyIndexChangeListeners(index); _singleton.notify(); @@ -52,7 +52,7 @@ class NavbarNotifier extends ChangeNotifier { /// Use to set the visibility of a badge using its [index]. static void makeBadgeVisible(int index, bool visible) { if (index < 0 || index >= length) return; - badges[index].showBadge = visible; + _badges[index] = _badges[index].copyWith(showBadge: visible); _notifyIndexChangeListeners(index); _singleton.notify(); } @@ -74,7 +74,7 @@ class NavbarNotifier extends ChangeNotifier { static set index(int x) { _index = x; - if (hideBadgeOnPageChanged) badges[x].clearBadge(); + if (hideBadgeOnPageChanged) makeBadgeVisible(x, false); if (_navbarStackHistory.contains(x)) { _navbarStackHistory.remove(x); } diff --git a/lib/src/navbar_router.dart b/lib/src/navbar_router.dart index 37c8798..a297a7e 100644 --- a/lib/src/navbar_router.dart +++ b/lib/src/navbar_router.dart @@ -204,7 +204,7 @@ class _NavbarRouterState extends State final navbaritem = widget.destinations[i].navbarItem; keys.add(GlobalKey()); items.add(navbaritem); - badges.add(navbaritem.badge ?? NavbarBadge()); + badges.add(navbaritem.badge ?? const NavbarBadge()); } // set badge list here NavbarNotifier.setKeys(keys, badgeList: badges); @@ -212,6 +212,8 @@ class _NavbarRouterState extends State if (!isUpdate) { initAnimation(); NavbarNotifier.index = widget.initialIndex; + // re-enable the initial badge that was hidden on setting NavbarNotifier.index + NavbarNotifier.makeBadgeVisible(NavbarNotifier.currentIndex, true); } } diff --git a/test/navbar_router_test.dart b/test/navbar_router_test.dart index a4a4f8a..05371eb 100644 --- a/test/navbar_router_test.dart +++ b/test/navbar_router_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:math'; +import 'package:badges/badges.dart' as badges; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:navbar_router/navbar_router.dart'; @@ -28,6 +29,7 @@ extension FindIcon on IconData { } void main() { + // updated test items with badges const List items = [ NavbarItem(Icons.home_outlined, 'Home', backgroundColor: mediumPurple, @@ -35,6 +37,11 @@ void main() { key: Key("HomeIconSelected"), Icons.home, size: 26, + ), + badge: NavbarBadge( + key: Key("TwoDigitBadge"), + badgeText: "10", + showBadge: true, )), NavbarItem(Icons.shopping_bag_outlined, 'Products', backgroundColor: Colors.orange, @@ -42,6 +49,11 @@ void main() { Icons.shopping_bag, key: Key("ProductsIconSelected"), size: 26, + ), + badge: NavbarBadge( + key: Key("OneDigitBadge"), + badgeText: "8", + showBadge: true, )), NavbarItem(Icons.person_outline, 'Me', backgroundColor: Colors.teal, @@ -49,12 +61,22 @@ void main() { key: Key("MeIconSelected"), Icons.person, size: 26, + ), + badge: NavbarBadge( + key: Key("DotBadge1"), + showBadge: true, + color: Colors.amber, )), NavbarItem(Icons.settings_outlined, 'Settings', backgroundColor: Colors.red, selectedIcon: Icon( Icons.settings, size: 26, + ), + badge: NavbarBadge( + key: Key("DotBadge2"), + showBadge: true, + color: Colors.red, )), ]; @@ -146,8 +168,176 @@ void main() { expect(destinationFinder, findsOneWidget); } + // function containing all badge tests and subtests + badgeGroupTest() { + badges.Badge findBadge(tester, index) { + return tester.widget(find.byKey(NavbarNotifier.badges[index].key!)) + as badges.Badge; + } + + // test color and visibility + testDot(tester, index) { + expect(find.byKey(NavbarNotifier.badges[index].key!), findsOneWidget); + + // test visibility + expect(findBadge(tester, index).showBadge, + NavbarNotifier.badges[index].showBadge); + + // test color + expect(findBadge(tester, index).badgeStyle.badgeColor, + NavbarNotifier.badges[index].color); + } + + /// Test badge + testBadgeWithText(tester, index) { + var textFind = find.text(NavbarNotifier.badges[index].badgeText); + expect(textFind, findsOneWidget); + + // test dot + testDot(tester, index); + + // compare the content of badge + Text text = tester.firstWidget(textFind); + expect(text.data, NavbarNotifier.badges[index].badgeText); + } + + desktopMode(tester) async { + await tester.pumpWidget(boilerplate(isDesktop: true)); + await tester.pumpAndSettle(); + expect(find.byType(NavigationRail), findsOneWidget); + expect(find.byType(BottomNavigationBar), findsNothing); + } + + testBadge(tester, index) async { + NavbarNotifier.badges[index].badgeText.isNotEmpty + ? testBadgeWithText(tester, index) + : testDot(tester, index); + NavbarNotifier.badges[index].badgeText.isNotEmpty + ? testBadgeWithText(tester, index) + : testDot(tester, index); + } + + testWidgets('Should build initial badges', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); + // test visibility + testBadge(tester, 0); + testBadge(tester, 1); + testBadge(tester, 2); + testBadge(tester, 3); + }); + + testWidgets('Should allow to update badges dynamically', + (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); + + // test badge + testBadge(tester, 0); + // update the whole badge + NavbarNotifier.updateBadge( + 0, + const NavbarBadge( + key: Key("TwoDigitBadgeNew"), + badgeText: "11", + showBadge: true, + )); + await tester.pumpAndSettle(); + + testBadge(tester, 0); + + // hide the badge + NavbarNotifier.makeBadgeVisible(0, false); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[0].showBadge, false); + testBadge(tester, 0); + }); + + testWidgets('Should allow to hide/show badges on demand', + (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); + + // test badge + testBadge(tester, 0); + + // hide all the badge + for (int i = 0; i < NavbarNotifier.length; i++) { + NavbarNotifier.makeBadgeVisible(i, false); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[i].showBadge, false); + testBadge(tester, i); + } + + // show all the badge + for (int i = 0; i < NavbarNotifier.length; i++) { + NavbarNotifier.makeBadgeVisible(i, true); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[i].showBadge, true); + testBadge(tester, i); + } + }); + + testWidgets('Desktop: should build initial badges', + (WidgetTester tester) async { + await desktopMode(tester); + // test visibility + testBadge(tester, 0); + testBadge(tester, 1); + testBadge(tester, 2); + testBadge(tester, 3); + }); + + testWidgets('Desktop: should allow to update badges dynamically', + (WidgetTester tester) async { + await desktopMode(tester); + + // test badge + testBadge(tester, 0); + // update the whole badge + NavbarNotifier.updateBadge( + 0, + const NavbarBadge( + key: Key("TwoDigitBadgeNew"), + badgeText: "11", + showBadge: true, + )); + await tester.pumpAndSettle(); + + testBadge(tester, 0); + + // hide the badge + NavbarNotifier.makeBadgeVisible(0, false); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[0].showBadge, false); + testBadge(tester, 0); + }); + + testWidgets('Desktop: should allow to hide/show badges on demand', + (WidgetTester tester) async { + await desktopMode(tester); + + // test badge + testBadge(tester, 0); + + // hide all the badge + for (int i = 0; i < NavbarNotifier.length; i++) { + NavbarNotifier.makeBadgeVisible(i, false); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[i].showBadge, false); + testBadge(tester, i); + } + + // show all the badge + for (int i = 0; i < NavbarNotifier.length; i++) { + NavbarNotifier.makeBadgeVisible(i, true); + await tester.pumpAndSettle(); + expect(NavbarNotifier.badges[i].showBadge, true); + testBadge(tester, i); + } + }); + } + group('Test NavbarType: NavbarType.standard ', () { - group('NavbarType.standard: should build destination and navbar items', () { + // test badges + group('Should build destination, navbar items, and badges', () { testWidgets('NavbarType.standard: should build destinations', (WidgetTester tester) async { final bottomNavigation = (BottomNavigationBar).typeX(); @@ -182,6 +372,10 @@ void main() { expect(find.text(items[2].text), findsOneWidget); }); + group('NavbarType.standard: badges test', () { + badgeGroupTest(); + }); + testWidgets( "NavbarType.standard: should allow updating navbar routes dynamically ", (WidgetTester tester) async { @@ -238,6 +432,7 @@ void main() { } }); }); + testWidgets('NavbarType.standard: default index must be zero', (WidgetTester tester) async { await tester.pumpWidget(boilerplate()); @@ -502,6 +697,10 @@ void main() { group('Test NavbarType: NavbarType.notched ', () { group('NavbarType.notched: should build destination and navbar items', () { + group('Badges test', () { + badgeGroupTest(); + }); + testWidgets('navbar_router should build destinations', (WidgetTester tester) async { final navbar = (NotchedNavBar).typeX(); @@ -817,6 +1016,9 @@ void main() { group( 'NavbarType.material3: should build destination and navbar items (Desktop)', () { + group('Badges test', () { + badgeGroupTest(); + }); testWidgets('navbar_router should build destinations', (WidgetTester tester) async { final navbar = (M3NavBar).typeX(); @@ -1358,6 +1560,9 @@ void main() { }); group('Test NavbarType: NavbarType.floating', () { + group('Badges test', () { + badgeGroupTest(); + }); testWidgets('NavbarType can be changed during runtime', (tester) async { NavbarType type = NavbarType.notched; await tester.pumpWidget(boilerplate(type: type)); From 9cbea0b2128f7ebf1cca2017fb8cd79a03ee4eb6 Mon Sep 17 00:00:00 2001 From: Minh Bao <74720131+bebaoboy@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:57:11 +0700 Subject: [PATCH 4/6] Update lib/src/navbar_notifier.dart as reviewed Co-authored-by: Mahesh Jamdade <31410839+maheshmnj@users.noreply.github.com> --- lib/src/navbar_notifier.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index b296759..fe6e666 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -45,7 +45,7 @@ class NavbarNotifier extends ChangeNotifier { if (index < 0 || index >= length) return; _badges[index] = badge; // TODO: wonder if this will cause performance issue - _notifyIndexChangeListeners(index); + NavbarNotifier.index = index; _singleton.notify(); } From 2262eae05efcd87ba5ca52de29e061336bca37ba Mon Sep 17 00:00:00 2001 From: Minh Bao Date: Mon, 22 Apr 2024 14:17:54 +0700 Subject: [PATCH 5/6] change to resolve reviews ;) --- lib/src/navbar_decoration.dart | 7 +++++-- lib/src/navbar_notifier.dart | 18 ++++++++++-------- lib/src/navbar_router.dart | 7 +++++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/src/navbar_decoration.dart b/lib/src/navbar_decoration.dart index 1b7a9d8..2c05972 100644 --- a/lib/src/navbar_decoration.dart +++ b/lib/src/navbar_decoration.dart @@ -3,7 +3,10 @@ import 'package:navbar_router/navbar_router.dart'; class NavbarItem { const NavbarItem(this.iconData, this.text, - {this.backgroundColor, this.child, this.selectedIcon, this.badge}); + {this.backgroundColor, + this.child, + this.selectedIcon, + this.badge = const NavbarBadge()}); /// IconData for the navbar item final IconData iconData; @@ -23,7 +26,7 @@ class NavbarItem { final Widget? selectedIcon; /// Your initial badge configuration for this item, this is totally optional - final NavbarBadge? badge; + final NavbarBadge badge; @override bool operator ==(Object other) => diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index fe6e666..f7b7a71 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -44,8 +44,10 @@ class NavbarNotifier extends ChangeNotifier { static void updateBadge(int index, NavbarBadge badge) { if (index < 0 || index >= length) return; _badges[index] = badge; - // TODO: wonder if this will cause performance issue - NavbarNotifier.index = index; + + // We don't navigate to that item when we update its badge. So cannot use this. + // NavbarNotifier.index = index; + _singleton.notify(); } @@ -53,16 +55,16 @@ class NavbarNotifier extends ChangeNotifier { static void makeBadgeVisible(int index, bool visible) { if (index < 0 || index >= length) return; _badges[index] = _badges[index].copyWith(showBadge: visible); - _notifyIndexChangeListeners(index); + _singleton.notify(); } - /// Conveniently setup the badges if user choose to show them. Also the only place that init the badges. - /// - /// Will throw AssertionError if length of keys and given badgeList are not the same - static void setKeys(List> value, - {List? badgeList}) { + static void setKeys(List> value) { _keys = value; + } + + /// The only place that init the badges. + static void setBadges(List? badgeList) { if (badgeList != null) { _badges = badgeList; } diff --git a/lib/src/navbar_router.dart b/lib/src/navbar_router.dart index a297a7e..55eb836 100644 --- a/lib/src/navbar_router.dart +++ b/lib/src/navbar_router.dart @@ -204,11 +204,14 @@ class _NavbarRouterState extends State final navbaritem = widget.destinations[i].navbarItem; keys.add(GlobalKey()); items.add(navbaritem); - badges.add(navbaritem.badge ?? const NavbarBadge()); + badges.add(navbaritem.badge); } + NavbarNotifier.setKeys(keys); + // set badge list here - NavbarNotifier.setKeys(keys, badgeList: badges); + NavbarNotifier.setBadges(badges); NavbarNotifier.hideBadgeOnPageChanged = widget.hideBadgeOnPageChanged; + if (!isUpdate) { initAnimation(); NavbarNotifier.index = widget.initialIndex; From 2335b6f0f6e4769dff7717db0026b6e52ffaae6b Mon Sep 17 00:00:00 2001 From: Minh Bao Date: Wed, 24 Apr 2024 17:53:53 +0700 Subject: [PATCH 6/6] edit the test case to include different navbar type --- lib/src/navbar_notifier.dart | 3 --- test/navbar_router_test.dart | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/src/navbar_notifier.dart b/lib/src/navbar_notifier.dart index f7b7a71..9365f0e 100644 --- a/lib/src/navbar_notifier.dart +++ b/lib/src/navbar_notifier.dart @@ -45,9 +45,6 @@ class NavbarNotifier extends ChangeNotifier { if (index < 0 || index >= length) return; _badges[index] = badge; - // We don't navigate to that item when we update its badge. So cannot use this. - // NavbarNotifier.index = index; - _singleton.notify(); } diff --git a/test/navbar_router_test.dart b/test/navbar_router_test.dart index 05371eb..56300f1 100644 --- a/test/navbar_router_test.dart +++ b/test/navbar_router_test.dart @@ -169,7 +169,7 @@ void main() { } // function containing all badge tests and subtests - badgeGroupTest() { + badgeGroupTest({NavbarType type = NavbarType.standard}) { badges.Badge findBadge(tester, index) { return tester.widget(find.byKey(NavbarNotifier.badges[index].key!)) as badges.Badge; @@ -202,7 +202,7 @@ void main() { } desktopMode(tester) async { - await tester.pumpWidget(boilerplate(isDesktop: true)); + await tester.pumpWidget(boilerplate(isDesktop: true, type: type)); await tester.pumpAndSettle(); expect(find.byType(NavigationRail), findsOneWidget); expect(find.byType(BottomNavigationBar), findsNothing); @@ -698,7 +698,7 @@ void main() { group('Test NavbarType: NavbarType.notched ', () { group('NavbarType.notched: should build destination and navbar items', () { group('Badges test', () { - badgeGroupTest(); + badgeGroupTest(type: NavbarType.notched); }); testWidgets('navbar_router should build destinations', @@ -1017,7 +1017,7 @@ void main() { 'NavbarType.material3: should build destination and navbar items (Desktop)', () { group('Badges test', () { - badgeGroupTest(); + badgeGroupTest(type: NavbarType.material3); }); testWidgets('navbar_router should build destinations', (WidgetTester tester) async { @@ -1561,7 +1561,7 @@ void main() { group('Test NavbarType: NavbarType.floating', () { group('Badges test', () { - badgeGroupTest(); + badgeGroupTest(type: NavbarType.floating); }); testWidgets('NavbarType can be changed during runtime', (tester) async { NavbarType type = NavbarType.notched;