import 'package:aves/app_mode.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/basic/draggable_scrollbar/notifications.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/identity/aves_app_bar.dart'; import 'package:aves/widgets/navigation/nav_bar/floating.dart'; import 'package:aves/widgets/navigation/nav_item.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class AppBottomNavBar extends StatefulWidget { final Stream events; // collection loaded in the `CollectionPage`, if any final CollectionLens? currentCollection; static double get height => kBottomNavigationBarHeight + AvesFloatingBar.margin.vertical; const AppBottomNavBar({ super.key, required this.events, this.currentCollection, }); @override State createState() => _AppBottomNavBarState(); } class _AppBottomNavBarState extends State { String? _lastRoute; @override void initState() { super.initState(); _registerWidget(widget); } @override void didUpdateWidget(covariant AppBottomNavBar oldWidget) { super.didUpdateWidget(oldWidget); _unregisterWidget(oldWidget); _registerWidget(widget); } @override void dispose() { _unregisterWidget(widget); super.dispose(); } void _registerWidget(AppBottomNavBar widget) { widget.currentCollection?.filterChangeNotifier.addListener(_onCollectionFilterChanged); } void _unregisterWidget(AppBottomNavBar widget) { widget.currentCollection?.filterChangeNotifier.removeListener(_onCollectionFilterChanged); } @override Widget build(BuildContext context) { final items = context.select>((v) => v.bottomNavigationActions); if (items.length < 2) return const SizedBox(); Widget child = FloatingNavBar( scrollController: PrimaryScrollController.of(context), events: widget.events, childHeight: AppBottomNavBar.height + context.select((mq) => mq.effectiveBottomPadding), child: SafeArea( child: AvesFloatingBar( builder: (context, backgroundColor, child) => BottomNavigationBar( items: items.map((item) { final label = item.getText(context); return BottomNavigationBarItem( icon: item.getIcon(context), label: label, tooltip: label, ); }).toList(), onTap: (index) => _goTo(context, items, index), currentIndex: _getCurrentIndex(context, items), type: BottomNavigationBarType.fixed, backgroundColor: backgroundColor, showSelectedLabels: false, showUnselectedLabels: false, ), ), ), ); final animate = context.select((v) => v.animate); if (animate) { child = Hero( tag: 'nav-bar', flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) { return MediaQuery.removeViewInsets( context: context, removeBottom: true, child: toHeroContext.widget, ); }, child: child, ); } return child; } void _onCollectionFilterChanged() => setState(() {}); int _getCurrentIndex(BuildContext context, List items) { // current route may be null during navigation final currentRoute = context.currentRouteName ?? _lastRoute; _lastRoute = currentRoute; final currentItem = items.firstWhereOrNull((item) { final itemRoute = item.route; if (currentRoute != itemRoute) return false; switch (itemRoute) { case CollectionPage.routeName: final currentFilters = widget.currentCollection?.filters ?? {}; return const SetEquality().equals(currentFilters, item.filters ?? {}); default: return true; } }); final currentIndex = currentItem != null ? items.indexOf(currentItem) : 0; return currentIndex; } void _goTo(BuildContext context, List items, int index) { final item = items[index]; item.goTo(context, topLevel: null); } } class NavBarPaddingSliver extends StatelessWidget { const NavBarPaddingSliver({super.key}); @override Widget build(BuildContext context) { final canNavigate = context.select, bool>((v) => v.value.canNavigate); final enableBottomNavigationBar = context.select((v) => v.enableBottomNavigationBar); final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; return SliverToBoxAdapter( child: SizedBox(height: showBottomNavigationBar ? AppBottomNavBar.height : 0), ); } }