From 2af92546cffe4f14d1a01a7cd6534901fedf0e23 Mon Sep 17 00:00:00 2001
From: Matthias Ngeo <matthiasngeo@gmail.com>
Date: Mon, 19 Aug 2024 17:40:39 +0800
Subject: [PATCH] Refactor bottom navigation bar

---
 docs/pages/docs/bottom-navigation-bar.mdx     |  4 +-
 forui/CHANGELOG.md                            |  8 +++-
 forui/example/lib/main.dart                   |  2 +-
 .../bottom_navigation_bar.dart                | 41 ++++++++++------
 .../bottom_navigation_bar_item.dart           | 48 +++++++++++--------
 .../lib/widgets/bottom_navigation_bar.dart    |  2 +-
 samples/lib/widgets/scaffold.dart             |  2 +-
 7 files changed, 65 insertions(+), 42 deletions(-)

diff --git a/docs/pages/docs/bottom-navigation-bar.mdx b/docs/pages/docs/bottom-navigation-bar.mdx
index fd55ade8e..04f9b9756 100644
--- a/docs/pages/docs/bottom-navigation-bar.mdx
+++ b/docs/pages/docs/bottom-navigation-bar.mdx
@@ -32,7 +32,7 @@ It is used to navigate between a small number of views, typically between three
         Widget build(BuildContext context) => FBottomNavigationBar(
               index: index,
               onChange: (index) => setState(() => this.index = index),
-              items: [
+              children: [
                 FBottomNavigationBarItem(
                   icon: FAssets.icons.home,
                   label: 'Home',
@@ -72,7 +72,7 @@ It is used to navigate between a small number of views, typically between three
 FBottomNavigationBar(
     index: 0,
     onChange: (index) => {},
-    items: [
+    children: [
       FBottomNavigationBarItem(
         icon: FAssets.icons.home,
         label: 'Home',
diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md
index aa7d8a359..da3ff2aa6 100644
--- a/forui/CHANGELOG.md
+++ b/forui/CHANGELOG.md
@@ -4,6 +4,8 @@
 
 * Add `FButton.icon(...)`.
 
+* Add `FBottomNavigationBar.of(...)`
+
 * Add `FFormFieldStyle`.
 
 * Add `FResizable.semanticFormatterCallback`.
@@ -16,6 +18,10 @@
 
 * **Breaking:** Change `FAlertIconStyle.height` to `FAlertIconStyle.size`.
 
+* **Breaking:** Rename `FBottomNavigationBar.items` to `FBottomNavigationBar.children`.
+
+* **Breaking:** Remove `FBottomNavigationBar.raw(...)` - use the default constructor instead.
+
 * Change `FResizable` to resize by `FResizable.resizePercentage` when using a keyboard.
 
 * **Breaking:** Change `FResiableDividerStyle.thickness` to `FResizableDividerStyle.width`.
@@ -28,7 +34,7 @@
 
 * **Breaking:** Change how `FTextFieldStyle` stores various state-dependent styles.
 
-* **Breaking:** Remove `FTextField.error`.
+* **Breaking:** Remove `FTextField.error` - use `FTextField.forceErrorText` instead.
 
 ### Fixes
 
diff --git a/forui/example/lib/main.dart b/forui/example/lib/main.dart
index 4f98061d2..eb7037ad7 100644
--- a/forui/example/lib/main.dart
+++ b/forui/example/lib/main.dart
@@ -44,7 +44,7 @@ class _ApplicationState extends State<Application> {
             footer: FBottomNavigationBar(
               index: index,
               onChange: (index) => setState(() => this.index = index),
-              items: [
+              children: [
                 FBottomNavigationBarItem(
                   icon: FAssets.icons.home,
                   label: 'Home',
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 8b6861484..b26550b75 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
@@ -1,7 +1,6 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
-import 'package:collection/collection.dart';
 import 'package:meta/meta.dart';
 
 import 'package:forui/forui.dart';
@@ -15,16 +14,18 @@ import 'package:forui/src/foundation/tappable.dart';
 /// See:
 /// * https://forui.dev/docs/bottom-navigation-bar for working examples.
 /// * [FBottomNavigationBarStyle] for customizing a bottom navigation bar's appearance.
+/// * [FBottomNavigationBarItem] for the items in a bottom navigation bar.
 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>();
+  @useResult
+  static FBottomNavigationBarData of(BuildContext context) {
+    final result = context.dependOnInheritedWidgetOfExactType<FBottomNavigationBarData>();
     assert(result != null, 'No _InheritedData found in context');
-    return (itemStyle: result!.itemStyle, current: result.current);
+    return result!;
   }
 
   /// The style.
@@ -34,19 +35,24 @@ class FBottomNavigationBar extends StatelessWidget {
   final ValueChanged<int>? onChange;
 
   /// The index.
+  ///
+  /// ## Contract
+  /// Throws [AssertionError] if [index] is not null and is negative.
   final int? index;
 
   /// The children.
   final List<Widget> children;
 
   /// Creates a [FBottomNavigationBar] with [FBottomNavigationBarItem]s.
+  ///
+  /// See [FBottomNavigationBarItem] for the items in a bottom navigation bar.
   const FBottomNavigationBar({
     required this.children,
     this.style,
     this.onChange,
     this.index,
     super.key,
-  });
+  }) : assert(index == null || 0 <= index, 'index must be null or non-negative.');
 
   @override
   Widget build(BuildContext context) {
@@ -69,9 +75,9 @@ class FBottomNavigationBar extends StatelessWidget {
                     onPress: () {
                       onChange?.call(i);
                     },
-                    child: _InheritedData(
+                    child: FBottomNavigationBarData(
                       itemStyle: style.itemStyle,
-                      current: index == i,
+                      selected: index == i,
                       child: child,
                     ),
                   ),
@@ -93,25 +99,30 @@ class FBottomNavigationBar extends StatelessWidget {
   }
 }
 
-class _InheritedData extends InheritedWidget {
+/// AFBottomNavigationBar]'s data.
+class FBottomNavigationBarData extends InheritedWidget {
+  /// The item's style.
   final FBottomNavigationBarItemStyle itemStyle;
-  final bool current;
+  /// Whether the item is currently selected.
+  final bool selected;
 
-  const _InheritedData({
+  /// Creates a [FBottomNavigationBarData].
+  const FBottomNavigationBarData({
     required this.itemStyle,
-    required this.current,
+    required this.selected,
     required super.child,
+    super.key,
   });
 
   @override
-  bool updateShouldNotify(_InheritedData old) => old.itemStyle != itemStyle || old.current != current;
+  bool updateShouldNotify(FBottomNavigationBarData old) => old.itemStyle != itemStyle || old.selected != selected;
 
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties
       ..add(DiagnosticsProperty('style', itemStyle))
-      ..add(FlagProperty('current', value: current, ifTrue: 'current'));
+      ..add(FlagProperty('selected', value: selected, ifTrue: 'selected'));
   }
 }
 
@@ -150,12 +161,12 @@ class FBottomNavigationBarStyle with Diagnosticable {
   FBottomNavigationBarStyle copyWith({
     BoxDecoration? decoration,
     EdgeInsets? padding,
-    FBottomNavigationBarItemStyle? item,
+    FBottomNavigationBarItemStyle? itemStyle,
   }) =>
       FBottomNavigationBarStyle(
         decoration: decoration ?? this.decoration,
         padding: padding ?? this.padding,
-        itemStyle: item ?? this.itemStyle,
+        itemStyle: itemStyle ?? this.itemStyle,
       );
 
   @override
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 809c1d30e..44f7de777 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
@@ -24,30 +24,36 @@ class FBottomNavigationBarItem extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final (:itemStyle, :current) = FBottomNavigationBar.of(context);
+    final FBottomNavigationBarData(:itemStyle, :selected) = FBottomNavigationBar.of(context);
     final style = this.style ?? itemStyle;
 
-    return MouseRegion(
-      cursor: SystemMouseCursors.click,
-      child: Padding(
-        padding: style.padding,
-        child: Column(
-          mainAxisSize: MainAxisSize.min,
-          children: [
-            icon(
-              height: style.iconSize,
-              colorFilter: ColorFilter.mode(
-                current ? style.activeIconColor : style.inactiveIconColor,
-                BlendMode.srcIn,
+    return Semantics(
+      button: true,
+      selected: selected,
+      label: label,
+      excludeSemantics: true,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        child: Padding(
+          padding: style.padding,
+          child: Column(
+            mainAxisSize: MainAxisSize.min,
+            children: [
+              icon(
+                height: style.iconSize,
+                colorFilter: ColorFilter.mode(
+                  selected ? style.activeIconColor : style.inactiveIconColor,
+                  BlendMode.srcIn,
+                ),
               ),
-            ),
-            const SizedBox(height: 2),
-            Text(
-              label,
-              overflow: TextOverflow.ellipsis,
-              style: current ? style.activeTextStyle : style.inactiveTextStyle,
-            ),
-          ],
+              const SizedBox(height: 2),
+              Text(
+                label,
+                overflow: TextOverflow.ellipsis,
+                style: selected ? style.activeTextStyle : style.inactiveTextStyle,
+              ),
+            ],
+          ),
         ),
       ),
     );
diff --git a/samples/lib/widgets/bottom_navigation_bar.dart b/samples/lib/widgets/bottom_navigation_bar.dart
index 6ff466622..927f0f0ef 100644
--- a/samples/lib/widgets/bottom_navigation_bar.dart
+++ b/samples/lib/widgets/bottom_navigation_bar.dart
@@ -32,7 +32,7 @@ class _DemoState extends State<_Demo> {
   Widget build(BuildContext context) => FBottomNavigationBar(
         index: index,
         onChange: (index) => setState(() => this.index = index),
-        items: [
+        children: [
           FBottomNavigationBarItem(
             icon: FAssets.icons.home,
             label: 'Home',
diff --git a/samples/lib/widgets/scaffold.dart b/samples/lib/widgets/scaffold.dart
index 2ee384203..20455a5a5 100644
--- a/samples/lib/widgets/scaffold.dart
+++ b/samples/lib/widgets/scaffold.dart
@@ -97,7 +97,7 @@ class _DemoState extends State<_Demo> {
         footer: FBottomNavigationBar(
           index: index,
           onChange: (index) => setState(() => this.index = index),
-          items: [
+          children: [
             FBottomNavigationBarItem(
               icon: FAssets.icons.home,
               label: 'Home',