#222 optional bottom nav bar

This commit is contained in:
Thibault Deckers 2022-05-06 18:02:38 +09:00
parent 37fc57f694
commit 39aebf49e2
21 changed files with 547 additions and 206 deletions

View file

@ -599,6 +599,7 @@
"settingsSectionNavigation": "Navigation", "settingsSectionNavigation": "Navigation",
"settingsHome": "Home", "settingsHome": "Home",
"settingsShowBottomNavigationBar": "Show bottom navigation bar",
"settingsKeepScreenOnTile": "Keep screen on", "settingsKeepScreenOnTile": "Keep screen on",
"settingsKeepScreenOnTitle": "Keep Screen On", "settingsKeepScreenOnTitle": "Keep Screen On",
"settingsDoubleBackExit": "Tap “back” twice to exit", "settingsDoubleBackExit": "Tap “back” twice to exit",

View file

@ -27,6 +27,7 @@ class SettingsDefaults {
static const mustBackTwiceToExit = true; static const mustBackTwiceToExit = true;
static const keepScreenOn = KeepScreenOn.viewerOnly; static const keepScreenOn = KeepScreenOn.viewerOnly;
static const homePage = HomePageSetting.collection; static const homePage = HomePageSetting.collection;
static const showBottomNavigationBar = false;
static const confirmDeleteForever = true; static const confirmDeleteForever = true;
static const confirmMoveToBin = true; static const confirmMoveToBin = true;
static const confirmMoveUndatedItems = true; static const confirmMoveUndatedItems = true;

View file

@ -56,6 +56,7 @@ class Settings extends ChangeNotifier {
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
static const keepScreenOnKey = 'keep_screen_on'; static const keepScreenOnKey = 'keep_screen_on';
static const homePageKey = 'home_page'; static const homePageKey = 'home_page';
static const showBottomNavigationBarKey = 'show_bottom_navigation_bar';
static const confirmDeleteForeverKey = 'confirm_delete_forever'; static const confirmDeleteForeverKey = 'confirm_delete_forever';
static const confirmMoveToBinKey = 'confirm_move_to_bin'; static const confirmMoveToBinKey = 'confirm_move_to_bin';
static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items'; static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items';
@ -294,6 +295,10 @@ class Settings extends ChangeNotifier {
set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString()); set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString());
bool get showBottomNavigationBar => getBoolOrDefault(showBottomNavigationBarKey, SettingsDefaults.showBottomNavigationBar);
set showBottomNavigationBar(bool newValue) => setAndNotify(showBottomNavigationBarKey, newValue);
bool get confirmDeleteForever => getBoolOrDefault(confirmDeleteForeverKey, SettingsDefaults.confirmDeleteForever); bool get confirmDeleteForever => getBoolOrDefault(confirmDeleteForeverKey, SettingsDefaults.confirmDeleteForever);
set confirmDeleteForever(bool newValue) => setAndNotify(confirmDeleteForeverKey, newValue); set confirmDeleteForever(bool newValue) => setAndNotify(confirmDeleteForeverKey, newValue);
@ -682,6 +687,7 @@ class Settings extends ChangeNotifier {
break; break;
case isInstalledAppAccessAllowedKey: case isInstalledAppAccessAllowedKey:
case isErrorReportingAllowedKey: case isErrorReportingAllowedKey:
case showBottomNavigationBarKey:
case mustBackTwiceToExitKey: case mustBackTwiceToExitKey:
case confirmDeleteForeverKey: case confirmDeleteForeverKey:
case confirmMoveToBinKey: case confirmMoveToBinKey:

View file

@ -139,7 +139,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
return AvesColorsProvider( return AvesColorsProvider(
child: child!, child: child!,
); );
// return child!;
}, },
onGenerateTitle: (context) => context.l10n.appName, onGenerateTitle: (context) => context.l10n.appName,
theme: Themes.lightTheme, theme: Themes.lightTheme,

View file

@ -16,7 +16,8 @@ import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/query_provider.dart'; import 'package:aves/widgets/common/providers/query_provider.dart';
import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart';
import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -74,7 +75,10 @@ class _CollectionPageState extends State<CollectionPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?; final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Scaffold( child: Selector<Settings, bool>(
selector: (context, s) => s.showBottomNavigationBar,
builder: (context, showBottomNavigationBar, child) {
return Scaffold(
body: SelectionProvider<AvesEntry>( body: SelectionProvider<AvesEntry>(
child: QueryProvider( child: QueryProvider(
initialQuery: liveFilter?.query, initialQuery: liveFilter?.query,
@ -108,7 +112,11 @@ class _CollectionPageState extends State<CollectionPage> {
), ),
), ),
drawer: AppDrawer(currentCollection: _collection), drawer: AppDrawer(currentCollection: _collection),
bottomNavigationBar: showBottomNavigationBar ? AppBottomNavBar(currentCollection: _collection) : null,
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
extendBody: true,
);
},
), ),
); );
} }

View file

@ -1,118 +0,0 @@
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/type.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/debug/app_debug_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:flutter/material.dart';
class DrawerFilterIcon extends StatelessWidget {
final CollectionFilter? filter;
const DrawerFilterIcon({
Key? key,
required this.filter,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconSize = 24 * textScaleFactor;
final _filter = filter;
if (_filter == null) return Icon(AIcons.allCollection, size: iconSize);
return _filter.iconBuilder(context, iconSize) ?? const SizedBox();
}
}
class DrawerFilterTitle extends StatelessWidget {
final CollectionFilter? filter;
const DrawerFilterTitle({
Key? key,
required this.filter,
}) : super(key: key);
@override
Widget build(BuildContext context) {
String _getString(CollectionFilter? filter) {
final l10n = context.l10n;
if (filter == null) return l10n.drawerCollectionAll;
if (filter == FavouriteFilter.instance) return l10n.drawerCollectionFavourites;
if (filter == MimeFilter.image) return l10n.drawerCollectionImages;
if (filter == MimeFilter.video) return l10n.drawerCollectionVideos;
if (filter == TypeFilter.animated) return l10n.drawerCollectionAnimated;
if (filter == TypeFilter.motionPhoto) return l10n.drawerCollectionMotionPhotos;
if (filter == TypeFilter.panorama) return l10n.drawerCollectionPanoramas;
if (filter == TypeFilter.raw) return l10n.drawerCollectionRaws;
if (filter == TypeFilter.sphericalVideo) return l10n.drawerCollectionSphericalVideos;
return filter.getLabel(context);
}
return Text(_getString(filter));
}
}
class DrawerPageIcon extends StatelessWidget {
final String route;
const DrawerPageIcon({
Key? key,
required this.route,
}) : super(key: key);
@override
Widget build(BuildContext context) {
switch (route) {
case AlbumListPage.routeName:
return const Icon(AIcons.album);
case CountryListPage.routeName:
return const Icon(AIcons.location);
case TagListPage.routeName:
return const Icon(AIcons.tag);
case AppDebugPage.routeName:
return ShaderMask(
shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn,
child: const Icon(AIcons.debug),
);
default:
return const SizedBox();
}
}
}
class DrawerPageTitle extends StatelessWidget {
final String route;
const DrawerPageTitle({
Key? key,
required this.route,
}) : super(key: key);
@override
Widget build(BuildContext context) {
String _getString() {
final l10n = context.l10n;
switch (route) {
case AlbumListPage.routeName:
return l10n.albumPageTitle;
case CountryListPage.routeName:
return l10n.countryPageTitle;
case TagListPage.routeName:
return l10n.tagPageTitle;
case AppDebugPage.routeName:
return 'Debug';
default:
return route;
}
}
return Text(_getString());
}
}

View file

@ -21,13 +21,14 @@ import 'package:aves/widgets/common/identity/scroll_thumb.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart'; import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:aves/widgets/drawer/app_drawer.dart';
import 'package:aves/widgets/filter_grids/common/covered_filter_chip.dart'; import 'package:aves/widgets/filter_grids/common/covered_filter_chip.dart';
import 'package:aves/widgets/filter_grids/common/draggable_thumb_label.dart'; import 'package:aves/widgets/filter_grids/common/draggable_thumb_label.dart';
import 'package:aves/widgets/filter_grids/common/filter_tile.dart'; import 'package:aves/widgets/filter_grids/common/filter_tile.dart';
import 'package:aves/widgets/filter_grids/common/list_details_theme.dart'; import 'package:aves/widgets/filter_grids/common/list_details_theme.dart';
import 'package:aves/widgets/filter_grids/common/section_keys.dart'; import 'package:aves/widgets/filter_grids/common/section_keys.dart';
import 'package:aves/widgets/filter_grids/common/section_layout.dart'; import 'package:aves/widgets/filter_grids/common/section_layout.dart';
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -69,7 +70,10 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Scaffold( child: Selector<Settings, bool>(
selector: (context, s) => s.showBottomNavigationBar,
builder: (context, showBottomNavigationBar, child) {
return Scaffold(
body: WillPopScope( body: WillPopScope(
onWillPop: () { onWillPop: () {
final selection = context.read<Selection<FilterGridItem<T>>>(); final selection = context.read<Selection<FilterGridItem<T>>>();
@ -104,7 +108,11 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
), ),
), ),
drawer: const AppDrawer(), drawer: const AppDrawer(),
bottomNavigationBar: showBottomNavigationBar ? const AppBottomNavBar() : null,
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
extendBody: true,
);
},
), ),
); );
} }

View file

@ -17,19 +17,19 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/identity/aves_logo.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart';
import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/debug/app_debug_page.dart';
import 'package:aves/widgets/drawer/collection_nav_tile.dart';
import 'package:aves/widgets/drawer/page_nav_tile.dart';
import 'package:aves/widgets/drawer/tile.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves/widgets/navigation/drawer/collection_nav_tile.dart';
import 'package:aves/widgets/navigation/drawer/page_nav_tile.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/settings/settings_page.dart'; import 'package:aves/widgets/settings/settings_page.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AppDrawer extends StatelessWidget { class AppDrawer extends StatefulWidget {
// collection loaded in the `CollectionPage`, if any // collection loaded in the `CollectionPage`, if any
final CollectionLens? currentCollection; final CollectionLens? currentCollection;
@ -38,6 +38,9 @@ class AppDrawer extends StatelessWidget {
this.currentCollection, this.currentCollection,
}) : super(key: key); }) : super(key: key);
@override
State<AppDrawer> createState() => _AppDrawerState();
static List<String> getDefaultAlbums(BuildContext context) { static List<String> getDefaultAlbums(BuildContext context) {
final source = context.read<CollectionSource>(); final source = context.read<CollectionSource>();
final specialAlbums = source.rawAlbums.where((album) { final specialAlbums = source.rawAlbums.where((album) {
@ -47,6 +50,14 @@ class AppDrawer extends StatelessWidget {
..sort(source.compareAlbumsByName); ..sort(source.compareAlbumsByName);
return specialAlbums; return specialAlbums;
} }
}
class _AppDrawerState extends State<AppDrawer> {
// using the default controller conflicts
// with bottom nav bar primary scroll monitoring
final ScrollController _scrollController = ScrollController();
CollectionLens? get currentCollection => widget.currentCollection;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -73,6 +84,7 @@ class AppDrawer extends StatelessWidget {
builder: (context, mqPaddingBottom, child) { builder: (context, mqPaddingBottom, child) {
final iconTheme = IconTheme.of(context); final iconTheme = IconTheme.of(context);
return SingleChildScrollView( return SingleChildScrollView(
controller: _scrollController,
// key is expected by test driver // key is expected by test driver
key: const Key('drawer-scrollview'), key: const Key('drawer-scrollview'),
padding: EdgeInsets.only(bottom: mqPaddingBottom), padding: EdgeInsets.only(bottom: mqPaddingBottom),

View file

@ -5,7 +5,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/drawer/tile.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/drawer/tile.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PageNavTile extends StatelessWidget { class PageNavTile extends StatelessWidget {

View file

@ -0,0 +1,81 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/debug/app_debug_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves/widgets/navigation/nav_display.dart';
import 'package:flutter/material.dart';
class DrawerFilterIcon extends StatelessWidget {
final CollectionFilter? filter;
const DrawerFilterIcon({
Key? key,
required this.filter,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconSize = 24 * textScaleFactor;
final _filter = filter;
if (_filter == null) return Icon(AIcons.allCollection, size: iconSize);
return _filter.iconBuilder(context, iconSize) ?? const SizedBox();
}
}
class DrawerFilterTitle extends StatelessWidget {
final CollectionFilter? filter;
const DrawerFilterTitle({
Key? key,
required this.filter,
}) : super(key: key);
@override
Widget build(BuildContext context) => Text(NavigationDisplay.getFilterTitle(context, filter));
}
class DrawerPageIcon extends StatelessWidget {
final String route;
const DrawerPageIcon({
Key? key,
required this.route,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final icon = NavigationDisplay.getPageIcon(route);
if (icon != null) {
switch (route) {
case AlbumListPage.routeName:
case CountryListPage.routeName:
case TagListPage.routeName:
return Icon(icon);
case AppDebugPage.routeName:
return ShaderMask(
shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn,
child: Icon(icon),
);
}
}
return const SizedBox();
}
}
class DrawerPageTitle extends StatelessWidget {
final String route;
const DrawerPageTitle({
Key? key,
required this.route,
}) : super(key: key);
@override
Widget build(BuildContext context) => Text(NavigationDisplay.getPageTitle(context, route));
}

View file

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
class FloatingNavBar extends StatefulWidget {
final ScrollController? scrollController;
final Widget child;
const FloatingNavBar({
Key? key,
required this.scrollController,
required this.child,
}) : super(key: key);
@override
_FloatingNavBarState createState() => _FloatingNavBarState();
}
class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
double? _lastOffset;
double _delta = 0;
static const double _deltaThreshold = 50;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0, 0),
end: const Offset(0, 1),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.linear,
))
..addListener(() {
if (mounted) {
setState(() {});
}
});
_registerWidget(widget);
}
@override
void didUpdateWidget(covariant FloatingNavBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.scrollController != widget.scrollController) {
_unregisterWidget(oldWidget);
_registerWidget(widget);
}
}
@override
void dispose() {
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(FloatingNavBar widget) {
_lastOffset = null;
_delta = 0;
widget.scrollController?.addListener(_onScrollChange);
}
void _unregisterWidget(FloatingNavBar widget) {
widget.scrollController?.removeListener(_onScrollChange);
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _offsetAnimation,
child: widget.child,
);
}
void _onScrollChange() {
final scrollController = widget.scrollController;
if (scrollController == null) return;
final offset = scrollController.offset;
_delta += offset - (_lastOffset ?? offset);
_lastOffset = offset;
if (_delta.abs() > _deltaThreshold) {
if (_delta > 0) {
// hide
_controller.forward();
} else {
// show
_controller.reverse();
}
_delta = 0;
}
}
}

View file

@ -0,0 +1,117 @@
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/navigation/nav_bar/floating.dart';
import 'package:aves/widgets/navigation/nav_bar/nav_item.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AppBottomNavBar extends StatelessWidget {
// collection loaded in the `CollectionPage`, if any
final CollectionLens? currentCollection;
const AppBottomNavBar({
Key? key,
this.currentCollection,
}) : super(key: key);
@override
Widget build(BuildContext context) {
const borderRadius = BorderRadius.all(Radius.circular(8));
final blurred = context.select<Settings, bool>((s) => s.enableOverlayBlurEffect);
final showVideo = context.select<Settings, bool>((s) => !s.hiddenFilters.contains(MimeFilter.video));
final items = [
const AvesBottomNavItem(route: CollectionPage.routeName),
if (showVideo) AvesBottomNavItem(route: CollectionPage.routeName, filter: MimeFilter.video),
const AvesBottomNavItem(route: CollectionPage.routeName, filter: FavouriteFilter.instance),
const AvesBottomNavItem(route: AlbumListPage.routeName),
];
Widget child = Padding(
padding: const EdgeInsets.all(8),
child: BlurredRRect(
enabled: blurred,
borderRadius: borderRadius,
child: BottomNavigationBar(
items: items
.map((item) => BottomNavigationBarItem(
icon: item.icon(context),
label: item.label(context),
))
.toList(),
onTap: (index) => _goTo(context, items, index),
currentIndex: _getCurrentIndex(context, items),
type: BottomNavigationBarType.fixed,
backgroundColor: Theme.of(context).canvasColor.withOpacity(.85),
showSelectedLabels: false,
showUnselectedLabels: false,
),
),
);
return Hero(
tag: 'nav-bar',
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
return MediaQuery.removeViewInsets(
context: context,
removeBottom: true,
child: toHero.widget,
);
},
child: FloatingNavBar(
scrollController: PrimaryScrollController.of(context),
child: SafeArea(
child: child,
),
),
);
}
int _getCurrentIndex(BuildContext context, List<AvesBottomNavItem> items) {
final currentRoute = context.currentRouteName;
final currentItem = items.firstWhereOrNull((item) {
if (currentRoute != item.route) return false;
if (item.route != CollectionPage.routeName) return true;
final currentFilters = currentCollection?.filters;
if (currentFilters == null || currentFilters.length > 1) return false;
return currentFilters.firstOrNull == item.filter;
});
final currentIndex = currentItem != null ? items.indexOf(currentItem) : 0;
return currentIndex;
}
void _goTo(BuildContext context, List<AvesBottomNavItem> items, int index) {
final item = items[index];
final routeName = item.route;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
settings: RouteSettings(name: routeName),
builder: (context) {
switch (routeName) {
case AlbumListPage.routeName:
return const AlbumListPage();
case CollectionPage.routeName:
default:
return CollectionPage(
source: context.read<CollectionSource>(),
filters: {item.filter},
);
}
},
),
(route) => false,
);
}
}

View file

@ -0,0 +1,36 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/navigation/nav_display.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
class AvesBottomNavItem extends Equatable {
final String route;
final CollectionFilter? filter;
@override
List<Object?> get props => [route, filter];
const AvesBottomNavItem({
required this.route,
this.filter,
});
Widget icon(BuildContext context) {
if (route == CollectionPage.routeName) {
return DrawerFilterIcon(filter: filter);
}
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconSize = 24 * textScaleFactor;
return Icon(NavigationDisplay.getPageIcon(route), size: iconSize);
}
String label(BuildContext context) {
if (route == CollectionPage.routeName) {
return NavigationDisplay.getFilterTitle(context, filter);
}
return NavigationDisplay.getPageTitle(context, route);
}
}

View file

@ -0,0 +1,59 @@
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/type.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/debug/app_debug_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class NavigationDisplay {
static String getFilterTitle(BuildContext context, CollectionFilter? filter) {
final l10n = context.l10n;
if (filter == null) return l10n.drawerCollectionAll;
if (filter == FavouriteFilter.instance) return l10n.drawerCollectionFavourites;
if (filter == MimeFilter.image) return l10n.drawerCollectionImages;
if (filter == MimeFilter.video) return l10n.drawerCollectionVideos;
if (filter == TypeFilter.animated) return l10n.drawerCollectionAnimated;
if (filter == TypeFilter.motionPhoto) return l10n.drawerCollectionMotionPhotos;
if (filter == TypeFilter.panorama) return l10n.drawerCollectionPanoramas;
if (filter == TypeFilter.raw) return l10n.drawerCollectionRaws;
if (filter == TypeFilter.sphericalVideo) return l10n.drawerCollectionSphericalVideos;
return filter.getLabel(context);
}
static String getPageTitle(BuildContext context, route) {
final l10n = context.l10n;
switch (route) {
case AlbumListPage.routeName:
return l10n.albumPageTitle;
case CountryListPage.routeName:
return l10n.countryPageTitle;
case TagListPage.routeName:
return l10n.tagPageTitle;
case AppDebugPage.routeName:
return 'Debug';
default:
return route;
}
}
static IconData? getPageIcon(String route) {
switch (route) {
case AlbumListPage.routeName:
return AIcons.album;
case CountryListPage.routeName:
return AIcons.location;
case TagListPage.routeName:
return AIcons.tag;
case AppDebugPage.routeName:
return AIcons.debug;
default:
return null;
}
}
}

View file

@ -31,6 +31,7 @@ class DisplaySection extends SettingsSection {
SettingsTileDisplayThemeBrightness(), SettingsTileDisplayThemeBrightness(),
SettingsTileDisplayThemeColorMode(), SettingsTileDisplayThemeColorMode(),
SettingsTileDisplayDisplayRefreshRateMode(), SettingsTileDisplayDisplayRefreshRateMode(),
SettingsTileDisplayEnableBlurEffect(),
]; ];
} }
@ -75,3 +76,15 @@ class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile {
dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle, dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle,
); );
} }
class SettingsTileDisplayEnableBlurEffect extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerEnableOverlayBlurEffect;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.enableOverlayBlurEffect,
onChanged: (v) => settings.enableOverlayBlurEffect = v,
title: title(context),
);
}

View file

@ -1,11 +1,11 @@
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/drawer/app_drawer.dart';
import 'package:aves/widgets/drawer/tile.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/settings/navigation/drawer_tab_albums.dart'; import 'package:aves/widgets/settings/navigation/drawer_tab_albums.dart';
import 'package:aves/widgets/settings/navigation/drawer_tab_fixed.dart'; import 'package:aves/widgets/settings/navigation/drawer_tab_fixed.dart';

View file

@ -3,8 +3,8 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons.dart'; import 'package:aves/widgets/common/identity/buttons.dart';
import 'package:aves/widgets/drawer/tile.dart';
import 'package:aves/widgets/filter_grids/album_pick.dart'; import 'package:aves/widgets/filter_grids/album_pick.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart'; import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View file

@ -31,6 +31,7 @@ class NavigationSection extends SettingsSection {
@override @override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [ FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileNavigationHomePage(), SettingsTileNavigationHomePage(),
SettingsTileShowBottomNavigationBar(),
SettingsTileNavigationDrawer(), SettingsTileNavigationDrawer(),
SettingsTileNavigationConfirmationDialog(), SettingsTileNavigationConfirmationDialog(),
SettingsTileNavigationKeepScreenOn(), SettingsTileNavigationKeepScreenOn(),
@ -53,6 +54,18 @@ class SettingsTileNavigationHomePage extends SettingsTile {
); );
} }
class SettingsTileShowBottomNavigationBar extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsShowBottomNavigationBar;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.showBottomNavigationBar,
onChanged: (v) => settings.showBottomNavigationBar = v,
title: title(context),
);
}
class SettingsTileNavigationDrawer extends SettingsTile { class SettingsTileNavigationDrawer extends SettingsTile {
@override @override
String title(BuildContext context) => context.l10n.settingsNavigationDrawerTile; String title(BuildContext context) => context.l10n.settingsNavigationDrawerTile;

View file

@ -52,11 +52,6 @@ class ViewerOverlayPage extends StatelessWidget {
onChanged: (v) => settings.showOverlayThumbnailPreview = v, onChanged: (v) => settings.showOverlayThumbnailPreview = v,
title: context.l10n.settingsViewerShowOverlayThumbnails, title: context.l10n.settingsViewerShowOverlayThumbnails,
), ),
SettingsSwitchListTile(
selector: (context, s) => s.enableOverlayBlurEffect,
onChanged: (v) => settings.enableOverlayBlurEffect = v,
title: context.l10n.settingsViewerEnableOverlayBlurEffect,
),
], ],
), ),
), ),

View file

@ -1,7 +1,8 @@
{ {
"de": [ "de": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"es": [ "es": [
@ -16,22 +17,26 @@
"appPickDialogTitle", "appPickDialogTitle",
"appPickDialogNone", "appPickDialogNone",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"fr": [ "fr": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"id": [ "id": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"it": [ "it": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"ja": [ "ja": [
@ -46,26 +51,31 @@
"appPickDialogTitle", "appPickDialogTitle",
"appPickDialogNone", "appPickDialogNone",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"ko": [ "ko": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"pt": [ "pt": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"ru": [ "ru": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
], ],
"zh": [ "zh": [
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty" "settingsSearchEmpty",
"settingsShowBottomNavigationBar"
] ]
} }