aves_mio/lib/widgets/navigation/nav_bar/nav_bar.dart
Fabio Micheluz 2c988f959b
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
first commit
2026-02-19 13:25:23 +01:00

153 lines
5 KiB
Dart

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<DraggableScrollbarEvent> 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<AppBottomNavBar> createState() => _AppBottomNavBarState();
}
class _AppBottomNavBarState extends State<AppBottomNavBar> {
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<Settings, List<AvesNavItem>>((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<MediaQueryData, double>((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<Settings, bool>((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<AvesNavItem> 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<AvesNavItem> 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<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
final enableBottomNavigationBar = context.select<Settings, bool>((v) => v.enableBottomNavigationBar);
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
return SliverToBoxAdapter(
child: SizedBox(height: showBottomNavigationBar ? AppBottomNavBar.height : 0),
);
}
}