diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 5b5b86338..ac2fdfc35 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -27,7 +27,7 @@ class SettingsDefaults { static const mustBackTwiceToExit = true; static const keepScreenOn = KeepScreenOn.viewerOnly; static const homePage = HomePageSetting.collection; - static const showBottomNavigationBar = false; + static const showBottomNavigationBar = true; static const confirmDeleteForever = true; static const confirmMoveToBin = true; static const confirmMoveUndatedItems = true; diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 7c25a05d1..9947415ae 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -44,6 +44,10 @@ class AvesApp extends StatefulWidget { static final GlobalKey navigatorKey = GlobalKey(debugLabel: 'app-navigator'); + // do not monitor all `ModalRoute`s, which would include popup menus, + // so that we can react to fullscreen `PageRoute`s only + static final RouteObserver pageRouteObserver = RouteObserver(); + const AvesApp({ super.key, required this.flavor, @@ -62,7 +66,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { // observers are not registered when using the same list object with different items // the list itself needs to be reassigned - List _navigatorObservers = []; + List _navigatorObservers = [AvesApp.pageRouteObserver]; final EventChannel _mediaStoreChangeChannel = const EventChannel('deckers.thibault/aves/media_store_change'); final EventChannel _newIntentChannel = const EventChannel('deckers.thibault/aves/intent'); final EventChannel _analysisCompletionChannel = const EventChannel('deckers.thibault/aves/analysis_events'); @@ -284,6 +288,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { 'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})', }); _navigatorObservers = [ + AvesApp.pageRouteObserver, ReportingRouteTracker(), ]; } diff --git a/lib/widgets/common/identity/aves_app_bar.dart b/lib/widgets/common/identity/aves_app_bar.dart index 1fba8daf6..a3199e672 100644 --- a/lib/widgets/common/identity/aves_app_bar.dart +++ b/lib/widgets/common/identity/aves_app_bar.dart @@ -1,5 +1,6 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -35,37 +36,38 @@ class AvesAppBar extends StatelessWidget { child: SafeArea( bottom: false, child: AvesFloatingBar( - builder: (context, backgroundColor) => Material( + builder: (context, backgroundColor, child) => Material( color: backgroundColor, textStyle: Theme.of(context).appBarTheme.titleTextStyle, - child: Column( - children: [ - SizedBox( - height: kToolbarHeight, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: leading, - ), - Expanded( - child: AnimatedSwitcher( - duration: context.read().iconAnimation, - child: Row( - key: ValueKey(transitionKey), - children: [ - Expanded(child: title), - ...actions, - ], - ), + child: child, + ), + child: Column( + children: [ + SizedBox( + height: kToolbarHeight, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: leading, + ), + Expanded( + child: AnimatedSwitcher( + duration: context.read().iconAnimation, + child: Row( + key: ValueKey(transitionKey), + children: [ + Expanded(child: title), + ...actions, + ], ), ), - ], - ), + ), + ], ), - if (bottom != null) bottom!, - ], - ), + ), + if (bottom != null) bottom!, + ], ), ), ), @@ -100,39 +102,75 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { bool shouldRebuild(covariant _SliverAppBarDelegate oldDelegate) => true; } -class AvesFloatingBar extends StatelessWidget { - final Widget Function(BuildContext context, Color backgroundColor) builder; +class AvesFloatingBar extends StatefulWidget { + final Widget Function(BuildContext context, Color backgroundColor, Widget? child) builder; + final Widget? child; + + static const margin = EdgeInsets.all(8); + static const borderRadius = BorderRadius.all(Radius.circular(8)); const AvesFloatingBar({ super.key, required this.builder, + this.child, }); - static const margin = EdgeInsets.all(8); - static const borderRadius = BorderRadius.all(Radius.circular(8)); + @override + State createState() => _AvesFloatingBarState(); +} + +class _AvesFloatingBarState extends State with RouteAware { + // prevent expensive blurring when the current page is hidden + final ValueNotifier _isBlurAllowedNotifier = ValueNotifier(true); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final route = ModalRoute.of(context); + if (route is PageRoute) { + AvesApp.pageRouteObserver.subscribe(this, route); + } + } + + @override + void dispose() { + AvesApp.pageRouteObserver.unsubscribe(this); + super.dispose(); + } + + @override + void didPopNext() => _isBlurAllowedNotifier.value = true; + + @override + void didPushNext() => _isBlurAllowedNotifier.value = false; @override Widget build(BuildContext context) { final theme = Theme.of(context); final backgroundColor = theme.appBarTheme.backgroundColor!; - final blurred = context.select((s) => s.enableOverlayBlurEffect); - - return Container( - foregroundDecoration: BoxDecoration( - border: Border.all( - color: theme.dividerColor, - ), - borderRadius: borderRadius, - ), - margin: margin, - child: BlurredRRect( - enabled: blurred, - borderRadius: borderRadius, - child: builder( - context, - blurred ? backgroundColor.withOpacity(.85) : backgroundColor, - ), - ), + return ValueListenableBuilder( + valueListenable: _isBlurAllowedNotifier, + builder: (context, isBlurAllowed, child) { + final blurred = isBlurAllowed && context.select((s) => s.enableOverlayBlurEffect); + return Container( + foregroundDecoration: BoxDecoration( + border: Border.all( + color: theme.dividerColor, + ), + borderRadius: AvesFloatingBar.borderRadius, + ), + margin: AvesFloatingBar.margin, + child: BlurredRRect( + enabled: blurred, + borderRadius: AvesFloatingBar.borderRadius, + child: widget.builder( + context, + blurred ? backgroundColor.withOpacity(.85) : backgroundColor, + widget.child, + ), + ), + ); + }, ); } } diff --git a/lib/widgets/navigation/nav_bar/nav_bar.dart b/lib/widgets/navigation/nav_bar/nav_bar.dart index 669b0d2b5..b947607c0 100644 --- a/lib/widgets/navigation/nav_bar/nav_bar.dart +++ b/lib/widgets/navigation/nav_bar/nav_bar.dart @@ -75,7 +75,7 @@ class _AppBottomNavBarState extends State { ]; Widget child = AvesFloatingBar( - builder: (context, backgroundColor) => BottomNavigationBar( + builder: (context, backgroundColor, child) => BottomNavigationBar( items: items .map((item) => BottomNavigationBarItem( icon: item.icon(context), diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index 2691e9314..3889cc2e9 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -20,6 +20,7 @@ Future configureAndLaunch() async { ..isErrorReportingAllowed = false ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection + ..showBottomNavigationBar = true ..setTileExtent(CountryListPage.routeName, 112) ..setTileLayout(CountryListPage.routeName, TileLayout.grid) // collection