diff --git a/CHANGELOG.md b/CHANGELOG.md index 5acffab71..1ed3b9e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Fixed `TreeView` selection state behavior for items that are not expanded ([#578](https://github.com/bdlukaa/fluent_ui/issues/578)) - Added support for Romanian language +- Ensure the body state in `NavigationView` is properly preserved ([#607](https://github.com/bdlukaa/fluent_ui/pull/607)) ## 4.0.3+1 diff --git a/lib/src/controls/navigation/navigation_view/body.dart b/lib/src/controls/navigation/navigation_view/body.dart index 25baeea2b..c5488dadd 100644 --- a/lib/src/controls/navigation/navigation_view/body.dart +++ b/lib/src/controls/navigation/navigation_view/body.dart @@ -14,7 +14,7 @@ class _NavigationBody extends StatefulWidget { // ignore: unused_element super.key, required this.itemKey, - required this.child, + this.paneBodyBuilder, this.transitionBuilder, // ignore: unused_element this.animationCurve, @@ -24,7 +24,7 @@ class _NavigationBody extends StatefulWidget { final ValueKey? itemKey; - final Widget child; + final NavigationContentBuilder? paneBodyBuilder; /// The transition builder. /// @@ -77,11 +77,25 @@ class _NavigationBody extends StatefulWidget { } class _NavigationBodyState extends State<_NavigationBody> { + final _pageKey = GlobalKey(); + final _pageController = PageController(); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final view = InheritedNavigationView.of(context); + + if (_pageController.hasClients && view.oldIndex != view.pane?.selected) { + _pageController.jumpToPage(view.pane?.selected ?? 0); + } + } + @override Widget build(BuildContext context) { assert(debugCheckHasFluentTheme(context)); - final view = InheritedNavigationView.maybeOf(context); + final view = InheritedNavigationView.of(context); final theme = FluentTheme.of(context); + return Container( color: theme.scaffoldBackgroundColor, child: AnimatedSwitcher( @@ -98,21 +112,19 @@ class _NavigationBodyState extends State<_NavigationBody> { return widget.transitionBuilder!(child, animation); } - if (view != null) { - bool isTop = view.displayMode == PaneDisplayMode.top; - - if (isTop) { - // Other transtitions other than default is only applied to top nav - // when clicking overflow on topnav, transition is from bottom - // otherwise if prevItem is on left side of nextActualItem, transition is from left - // if prevItem is on right side of nextActualItem, transition is from right - // click on Settings item is considered Default - return HorizontalSlidePageTransition( - animation: animation, - fromLeft: view.oldIndex < (view.pane?.selected ?? 0), - child: child, - ); - } + bool isTop = view.displayMode == PaneDisplayMode.top; + + if (isTop) { + // Other transtitions other than default is only applied to top nav + // when clicking overflow on topnav, transition is from bottom + // otherwise if prevItem is on left side of nextActualItem, transition is from left + // if prevItem is on right side of nextActualItem, transition is from right + // click on Settings item is considered Default + return HorizontalSlidePageTransition( + animation: animation, + fromLeft: view.oldIndex < (view.pane?.selected ?? 0), + child: child, + ); } return EntrancePageTransition( @@ -121,9 +133,27 @@ class _NavigationBodyState extends State<_NavigationBody> { child: child, ); }, - child: FocusTraversalGroup( + child: KeyedSubtree( key: widget.itemKey, - child: widget.child, + child: PageView.builder( + key: _pageKey, + physics: const NeverScrollableScrollPhysics(), + allowImplicitScrolling: false, + controller: _pageController, + itemCount: view.pane!.effectiveItems.length, + itemBuilder: (context, index) { + final bool isSelected = view.pane!.selected == index; + return view.pane!.effectiveItems.map((item) { + return ExcludeFocus( + key: item.bodyKey, + excluding: !isSelected, + child: FocusTraversalGroup( + child: widget.paneBodyBuilder?.call(item.body) ?? item.body, + ), + ); + }).elementAt(index); + }, + ), ), ), ); diff --git a/lib/src/controls/navigation/navigation_view/pane_items.dart b/lib/src/controls/navigation/navigation_view/pane_items.dart index e6e51cab1..35c8044a3 100644 --- a/lib/src/controls/navigation/navigation_view/pane_items.dart +++ b/lib/src/controls/navigation/navigation_view/pane_items.dart @@ -1,6 +1,12 @@ part of 'view.dart'; class NavigationPaneItem with Diagnosticable { + /// The key used for the item itself. Useful to find the position and size of + /// the pane item within the screen + /// + /// See also: + /// + /// * [PaneItem.build], which assigns GlobalKey itemKey = GlobalKey(); NavigationPaneItem(); @@ -21,6 +27,14 @@ class NavigationPaneItem with Diagnosticable { /// * [PaneItemAction], the item used for execute an action on click /// * [PaneItemExpander], which creates hierhical navigation class PaneItem extends NavigationPaneItem { + /// The key used for the body content + /// + /// See also: + /// + /// * [body], which this is assigned to + /// * [_NavigationBody], which assigns this to every pane body + GlobalKey bodyKey = GlobalKey(); + /// Creates a pane item. PaneItem({ required this.icon, diff --git a/lib/src/controls/navigation/navigation_view/view.dart b/lib/src/controls/navigation/navigation_view/view.dart index 37e5d84d5..e7b6b9222 100644 --- a/lib/src/controls/navigation/navigation_view/view.dart +++ b/lib/src/controls/navigation/navigation_view/view.dart @@ -19,6 +19,8 @@ part 'style.dart'; /// Value eyeballed from Windows 10 v10.0.19041.928 const double _kDefaultAppBarHeight = 50.0; +typedef NavigationContentBuilder = Widget Function(Widget? body); + /// The NavigationView control provides top-level navigation for your app. It /// adapts to a variety of screen sizes and supports both top and left /// navigation styles. @@ -56,14 +58,13 @@ class NavigationView extends StatefulWidget { /// Can be used to override the widget that is built from /// the [PaneItem.body]. Only used if [pane] is provided. - /// If nothing is selected, `selectedPaneItemBody` will be - /// null. + /// If nothing is selected, `body` will be null. /// /// This can be useful if you are using router-based navigation, /// and the body of the navigation pane is dynamically determined or /// affected by the current route rather than just by the currently /// selected pane. - final Widget Function(Widget? selectedPaneItemBody)? paneBodyBuilder; + final NavigationContentBuilder? paneBodyBuilder; /// The navigation pane, that can be displayed either on the /// left, on the top, or above the body. @@ -361,17 +362,12 @@ class NavigationViewState extends State { late Widget paneResult; if (widget.pane != null) { final pane = widget.pane!; - final paneBodyBuilder = widget.paneBodyBuilder; final body = _NavigationBody( itemKey: ValueKey(pane.selected ?? -1), transitionBuilder: widget.transitionBuilder, - child: paneBodyBuilder != null - ? paneBodyBuilder( - pane.selected != null ? pane.selectedItem.body : null) - : (pane.selected != null - ? pane.selectedItem.body - : const SizedBox.shrink()), + paneBodyBuilder: widget.paneBodyBuilder, ); + if (pane.customPane != null) { paneResult = Builder(builder: (context) { return PaneScrollConfiguration(