From 56037543f64883edc05e55870ea866487476b3ca Mon Sep 17 00:00:00 2001 From: Matthias Ngeo Date: Mon, 19 Aug 2024 12:39:50 +0800 Subject: [PATCH] Refactor FBottomNavigationBar TODO: update CHANGELOG TODO: Look through documentation and recommend people use `FBottomNavigationBar` --- .../bottom_navigation_bar.dart | 123 ++++++++++-------- .../bottom_navigation_bar_item.dart | 68 ++++------ forui/lib/widgets/bottom_navigation_bar.dart | 1 + .../bottom_navigation_bar_golden_test.dart | 2 +- 4 files changed, 90 insertions(+), 104 deletions(-) diff --git a/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart index 72436af69..8b6861484 100644 --- a/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart +++ b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart @@ -7,8 +7,6 @@ import 'package:meta/meta.dart'; import 'package:forui/forui.dart'; import 'package:forui/src/foundation/tappable.dart'; -part 'bottom_navigation_bar_item.dart'; - /// A bottom navigation bar. /// /// A bottom navigation bar is usually present at the bottom of root pages. It is used to navigate between a small @@ -16,46 +14,43 @@ part 'bottom_navigation_bar_item.dart'; /// /// See: /// * https://forui.dev/docs/bottom-navigation-bar for working examples. -/// * [FBottomNavigationBarStyle] for customizing a card's appearance. +/// * [FBottomNavigationBarStyle] for customizing a bottom navigation bar's appearance. class FBottomNavigationBar extends StatelessWidget { + /// Returns the [FBottomNavigationBarItemStyle] and currently selected index of the [FBottomNavigationBar] in the + /// given [context]. + /// + /// ## Contract + /// Throws [AssertionError] if there is no ancestor [FBottomNavigationBar] in the given [context]. + static ({FBottomNavigationBarItemStyle itemStyle, bool current}) of(BuildContext context) { + final _InheritedData? result = context.dependOnInheritedWidgetOfExactType<_InheritedData>(); + assert(result != null, 'No _InheritedData found in context'); + return (itemStyle: result!.itemStyle, current: result.current); + } + /// The style. final FBottomNavigationBarStyle? style; - /// The items. - final List items; - /// A callback for when an item is selected. final ValueChanged? onChange; + /// The index. + final int? index; + + /// The children. + final List children; + /// Creates a [FBottomNavigationBar] with [FBottomNavigationBarItem]s. - FBottomNavigationBar({ - required List items, - this.style, - this.onChange, - int index = -1, - super.key, - }) : items = items - .mapIndexed( - (currentIndex, item) => _FBottomNavigationBarItem( - item: item, - current: index == currentIndex, - style: style?.item, - ), - ) - .toList(); - - /// Creates a [FBottomNavigationBar] with [Widget]s. - const FBottomNavigationBar.raw({ - required this.items, + const FBottomNavigationBar({ + required this.children, this.style, this.onChange, + this.index, super.key, }); @override Widget build(BuildContext context) { final style = this.style ?? context.theme.bottomNavigationBarStyle; - return DecoratedBox( decoration: style.decoration, child: SafeArea( @@ -63,20 +58,25 @@ class FBottomNavigationBar extends StatelessWidget { bottom: false, child: Padding( padding: style.padding.copyWith( - bottom: style.padding.bottom + (MediaQuery.of(context).viewPadding.bottom * 2 / 3), + bottom: style.padding.bottom + (MediaQuery.viewPaddingOf(context).bottom * 2 / 3), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: items - .mapIndexed( - (index, item) => Expanded( - child: FTappable.animated( - onPress: () => onChange?.call(index), - child: item, + children: [ + for (final (i, child) in children.indexed) + Expanded( + child: FTappable.animated( + onPress: () { + onChange?.call(i); + }, + child: _InheritedData( + itemStyle: style.itemStyle, + current: index == i, + child: child, ), ), - ) - .toList(), + ), + ], ), ), ), @@ -88,8 +88,30 @@ class FBottomNavigationBar extends StatelessWidget { super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('style', style)) - ..add(IterableProperty('items', items)) - ..add(ObjectFlagProperty.has('onSelect', onChange)); + ..add(ObjectFlagProperty.has('onChange', onChange)) + ..add(IntProperty('initialIndex', index)); + } +} + +class _InheritedData extends InheritedWidget { + final FBottomNavigationBarItemStyle itemStyle; + final bool current; + + const _InheritedData({ + required this.itemStyle, + required this.current, + required super.child, + }); + + @override + bool updateShouldNotify(_InheritedData old) => old.itemStyle != itemStyle || old.current != current; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('style', itemStyle)) + ..add(FlagProperty('current', value: current, ifTrue: 'current')); } } @@ -102,12 +124,12 @@ class FBottomNavigationBarStyle with Diagnosticable { final EdgeInsets padding; /// The item's style. - final FBottomNavigationBarItemStyle item; + final FBottomNavigationBarItemStyle itemStyle; /// Creates a [FBottomNavigationBarStyle]. FBottomNavigationBarStyle({ required this.decoration, - required this.item, + required this.itemStyle, this.padding = const EdgeInsets.all(5), }); @@ -118,25 +140,12 @@ class FBottomNavigationBarStyle with Diagnosticable { color: colorScheme.background, ), padding = const EdgeInsets.all(5), - item = FBottomNavigationBarItemStyle.inherit( + itemStyle = FBottomNavigationBarItemStyle.inherit( colorScheme: colorScheme, typography: typography, ); /// Returns a copy of this [FBottomNavigationBarStyle] with the given properties replaced. - /// - /// ```dart - /// final style = FBottomNavigationBarStyle( - /// decoration: ..., - /// padding: ..., - /// ... - /// ); - /// - /// final copy = style.copyWith(padding: ...); - /// - /// print(style.decoration == copy.decoration); // true - /// print(style.padding == copy.padding); // false - /// ``` @useResult FBottomNavigationBarStyle copyWith({ BoxDecoration? decoration, @@ -146,7 +155,7 @@ class FBottomNavigationBarStyle with Diagnosticable { FBottomNavigationBarStyle( decoration: decoration ?? this.decoration, padding: padding ?? this.padding, - item: item ?? this.item, + itemStyle: item ?? this.itemStyle, ); @override @@ -155,7 +164,7 @@ class FBottomNavigationBarStyle with Diagnosticable { properties ..add(DiagnosticsProperty('decoration', decoration)) ..add(DiagnosticsProperty('padding', padding)) - ..add(DiagnosticsProperty('item', item)); + ..add(DiagnosticsProperty('itemStyle', itemStyle)); } @override @@ -165,8 +174,8 @@ class FBottomNavigationBarStyle with Diagnosticable { runtimeType == other.runtimeType && decoration == other.decoration && padding == other.padding && - item == other.item; + itemStyle == other.itemStyle; @override - int get hashCode => decoration.hashCode ^ padding.hashCode ^ item.hashCode; + int get hashCode => decoration.hashCode ^ padding.hashCode ^ itemStyle.hashCode; } diff --git a/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart index 2b353b86f..809c1d30e 100644 --- a/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart +++ b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart @@ -1,21 +1,31 @@ -part of 'bottom_navigation_bar.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:forui/forui.dart'; +import 'package:meta/meta.dart'; -class _FBottomNavigationBarItem extends StatelessWidget { +/// A [FBottomNavigationBar] item. +class FBottomNavigationBarItem extends StatelessWidget { + /// The style. final FBottomNavigationBarItemStyle? style; - final FBottomNavigationBarItem item; + /// The icon. + final SvgAsset icon; - final bool current; + /// The label. + final String label; - const _FBottomNavigationBarItem({ - required this.item, - required this.current, + /// Creates a [FBottomNavigationBarItem]. + const FBottomNavigationBarItem({ + required this.icon, + required this.label, this.style, + super.key, }); @override Widget build(BuildContext context) { - final style = item.style ?? this.style ?? context.theme.bottomNavigationBarStyle.item; + final (:itemStyle, :current) = FBottomNavigationBar.of(context); + final style = this.style ?? itemStyle; return MouseRegion( cursor: SystemMouseCursors.click, @@ -24,7 +34,7 @@ class _FBottomNavigationBarItem extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - item.icon( + icon( height: style.iconSize, colorFilter: ColorFilter.mode( current ? style.activeIconColor : style.inactiveIconColor, @@ -33,7 +43,7 @@ class _FBottomNavigationBarItem extends StatelessWidget { ), const SizedBox(height: 2), Text( - item.label, + label, overflow: TextOverflow.ellipsis, style: current ? style.activeTextStyle : style.inactiveTextStyle, ), @@ -48,30 +58,11 @@ class _FBottomNavigationBarItem extends StatelessWidget { super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('style', style)) - ..add(DiagnosticsProperty('item', item)) - ..add(FlagProperty('active', value: current, ifTrue: 'active')); + ..add(DiagnosticsProperty('icon', icon)) + ..add(StringProperty('label', label)); } } -/// A [FBottomNavigationBar] item. -class FBottomNavigationBarItem { - /// The style. - final FBottomNavigationBarItemStyle? style; - - /// The icon. - final SvgAsset icon; - - /// The label. - final String label; - - /// Creates a [FBottomNavigationBarItem]. - const FBottomNavigationBarItem({ - required this.icon, - required this.label, - this.style, - }); -} - /// [FBottomNavigationBarItem]'s style. class FBottomNavigationBarItemStyle with Diagnosticable { /// The icon's size. Defaults to `28`. @@ -118,21 +109,6 @@ class FBottomNavigationBarItemStyle with Diagnosticable { padding = const EdgeInsets.all(5); /// Returns a copy of this [FBottomNavigationBarItemStyle] with the given properties replaced. - /// - /// ```dart - /// final style = FBottomNavigationBarItemStyle( - /// activeIconColor: Colors.black, - /// inactiveIconColor: Colors.white, - /// ... - /// ); - /// - /// final copy = style.copyWith( - /// inactiveIconColor: Colors.blue, - /// ); - /// - /// print(copy.activeIconColor); // black - /// print(copy.inactiveIconColor); // blue - /// ``` @useResult FBottomNavigationBarItemStyle copyWith({ double? iconSize, diff --git a/forui/lib/widgets/bottom_navigation_bar.dart b/forui/lib/widgets/bottom_navigation_bar.dart index 63f36dfd2..74503f377 100644 --- a/forui/lib/widgets/bottom_navigation_bar.dart +++ b/forui/lib/widgets/bottom_navigation_bar.dart @@ -6,3 +6,4 @@ library forui.widgets.bottom_navigation_bar; export '../src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart'; +export '../src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart'; diff --git a/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart b/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart index eda459b75..f87af65b4 100644 --- a/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart +++ b/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart @@ -16,7 +16,7 @@ void main() { background: background, child: FBottomNavigationBar( index: 2, - items: [ + children: [ FBottomNavigationBarItem( icon: FAssets.icons.home, label: 'Home',