diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 160d42ae2..1e44c52e5 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -198,14 +198,6 @@ class Settings extends ChangeNotifier { set searchHistory(List newValue) => setAndNotify(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList()); - // utils - - // `RoutePredicate` - RoutePredicate navRemoveRoutePredicate(String pushedRouteName) { - final home = homePage.routeName; - return (route) => pushedRouteName != home && route.settings?.name == home; - } - // convenience methods // ignore: avoid_positional_boolean_parameters diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 937e2b771..c0145fc58 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -374,7 +374,8 @@ class _CollectionAppBarState extends State with SingleTickerPr MaterialPageRoute( settings: RouteSettings(name: StatsPage.routeName), builder: (context) => StatsPage( - collection: collection, + source: source, + parentCollection: collection, ), ), ); diff --git a/lib/widgets/common/aves_filter_chip.dart b/lib/widgets/common/aves_filter_chip.dart index 89b40b0d6..314a2a38b 100644 --- a/lib/widgets/common/aves_filter_chip.dart +++ b/lib/widgets/common/aves_filter_chip.dart @@ -111,7 +111,7 @@ class _AvesFilterChipState extends State { mainAxisSize: MainAxisSize.min, children: [ content, - widget.details, + Flexible(child: widget.details), ], ); } diff --git a/lib/widgets/drawer/collection_tile.dart b/lib/widgets/drawer/collection_tile.dart index 9cff6347f..e255a8059 100644 --- a/lib/widgets/drawer/collection_tile.dart +++ b/lib/widgets/drawer/collection_tile.dart @@ -51,7 +51,7 @@ class CollectionNavTile extends StatelessWidget { sortFactor: settings.collectionSortFactor, )), ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), + (route) => false, ); } } diff --git a/lib/widgets/drawer/tile.dart b/lib/widgets/drawer/tile.dart index 6b044cd98..3fbbfbecb 100644 --- a/lib/widgets/drawer/tile.dart +++ b/lib/widgets/drawer/tile.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/utils/flutter_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -52,7 +51,7 @@ class NavTile extends StatelessWidget { Navigator.pushAndRemoveUntil( context, route, - settings.navRemoveRoutePredicate(routeName), + (route) => false, ); } else { Navigator.push(context, route); diff --git a/lib/widgets/filter_grids/common/chip_set_action_delegate.dart b/lib/widgets/filter_grids/common/chip_set_action_delegate.dart index 6f507e17d..b71436362 100644 --- a/lib/widgets/filter_grids/common/chip_set_action_delegate.dart +++ b/lib/widgets/filter_grids/common/chip_set_action_delegate.dart @@ -1,5 +1,4 @@ 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/model/source/enums.dart'; import 'package:aves/widgets/common/aves_selection_dialog.dart'; @@ -52,11 +51,7 @@ abstract class ChipSetActionDelegate { MaterialPageRoute( settings: RouteSettings(name: StatsPage.routeName), builder: (context) => StatsPage( - collection: CollectionLens( - source: source, - groupFactor: settings.collectionGroupFactor, - sortFactor: settings.collectionSortFactor, - ), + source: source, ), ), ); diff --git a/lib/widgets/filter_grids/common/decorated_filter_chip.dart b/lib/widgets/filter_grids/common/decorated_filter_chip.dart index 541949993..19a2d4908 100644 --- a/lib/widgets/filter_grids/common/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/common/decorated_filter_chip.dart @@ -64,30 +64,21 @@ class DecoratedFilterChip extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - AnimatedSwitcher( - duration: Durations.chipDecorationAnimation, - switchInCurve: Curves.easeInOutCubic, - switchOutCurve: Curves.easeInOutCubic, - transitionBuilder: (child, animation) => FadeTransition( - opacity: animation, - child: SizeTransition( - axis: Axis.horizontal, - sizeFactor: animation, - axisAlignment: 1.0, - child: child, + AnimatedCrossFade( + firstChild: Padding( + padding: EdgeInsets.only(right: 8), + child: DecoratedIcon( + AIcons.pin, + color: FilterGridPage.detailColor, + shadows: [Constants.embossShadow], + size: 16, ), ), - child: pinned - ? Padding( - padding: EdgeInsets.only(right: 8), - child: DecoratedIcon( - AIcons.pin, - color: FilterGridPage.detailColor, - shadows: [Constants.embossShadow], - size: 16, - ), - ) - : SizedBox.shrink(), + secondChild: SizedBox.shrink(), + sizeCurve: Curves.easeInOutCubic, + alignment: AlignmentDirectional.centerEnd, + crossFadeState: pinned ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: Durations.chipDecorationAnimation, ), if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)) Padding( diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 012e8ad40..33288111d 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -76,7 +76,7 @@ class FilterNavigationPage extends StatelessWidget { return sourceState != SourceState.loading && emptyBuilder != null ? emptyBuilder() : SizedBox.shrink(); }, ), - onTap: (filter) => Navigator.pushAndRemoveUntil( + onTap: (filter) => Navigator.push( context, MaterialPageRoute( settings: RouteSettings(name: CollectionPage.routeName), @@ -87,7 +87,6 @@ class FilterNavigationPage extends StatelessWidget { sortFactor: settings.collectionSortFactor, )), ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), ), onLongPress: AvesApp.mode == AppMode.main ? (filter, tapPosition) => _showMenu(context, filter, tapPosition) : null, ); diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 11b25828f..bbb8b3011 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -296,7 +296,7 @@ class FullscreenBodyState extends State with SingleTickerProvide settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(collection.derive(filter)), ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), + (route) => false, ); } diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index b7b343bd1..4e64b4e29 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -22,8 +22,8 @@ import 'package:flutter/services.dart'; class ImageSearchDelegate { final CollectionSource source; - final ValueNotifier expandedSectionNotifier = ValueNotifier(null); final CollectionLens parentCollection; + final ValueNotifier expandedSectionNotifier = ValueNotifier(null); static const searchHistoryCount = 10; @@ -188,14 +188,12 @@ class ImageSearchDelegate { if (parentCollection != null) { _applyToParentCollectionPage(context, filter); } else { - _goToCollectionPage(context, filter); + _jumpToCollectionPage(context, filter); } } void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) { - if (filter != null) { - parentCollection.addFilter(filter); - } + parentCollection.addFilter(filter); // we post closing the search page after applying the filter selection // so that hero animation target is ready in the `FilterBar`, // even when the target is a child of an `AnimatedList` @@ -209,7 +207,7 @@ class ImageSearchDelegate { Navigator.pop(context); } - void _goToCollectionPage(BuildContext context, CollectionFilter filter) { + void _jumpToCollectionPage(BuildContext context, CollectionFilter filter) { _clean(); Navigator.pushAndRemoveUntil( context, @@ -222,7 +220,7 @@ class ImageSearchDelegate { sortFactor: settings.collectionSortFactor, )), ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), + (route) => false, ); } diff --git a/lib/widgets/stats/filter_table.dart b/lib/widgets/stats/filter_table.dart index 6c64666d5..ed12727e5 100644 --- a/lib/widgets/stats/filter_table.dart +++ b/lib/widgets/stats/filter_table.dart @@ -1,9 +1,6 @@ import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -11,14 +8,16 @@ import 'package:intl/intl.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; class FilterTable extends StatelessWidget { - final CollectionLens collection; + final int totalEntryCount; final Map entryCountMap; final CollectionFilter Function(String key) filterBuilder; + final FilterCallback onFilterSelection; const FilterTable({ - @required this.collection, + @required this.totalEntryCount, @required this.entryCountMap, @required this.filterBuilder, + @required this.onFilterSelection, }); static const chipWidth = AvesFilterChip.maxChipWidth; @@ -27,7 +26,6 @@ class FilterTable extends StatelessWidget { @override Widget build(BuildContext context) { - final maxCount = collection.entryCount; final sortedEntries = entryCountMap.entries.toList() ..sort((kv1, kv2) { final c = kv2.value.compareTo(kv1.value); @@ -47,7 +45,7 @@ class FilterTable extends StatelessWidget { final filter = filterBuilder(kv.key); final label = filter.label; final count = kv.value; - final percent = count / maxCount; + final percent = count / totalEntryCount; return TableRow( children: [ Container( @@ -58,7 +56,7 @@ class FilterTable extends StatelessWidget { alignment: AlignmentDirectional.centerStart, child: AvesFilterChip( filter: filter, - onTap: (filter) => _goToCollection(context, filter), + onTap: onFilterSelection, ), ), if (showPercentIndicator) @@ -92,16 +90,4 @@ class FilterTable extends StatelessWidget { ), ); } - - void _goToCollection(BuildContext context, CollectionFilter filter) { - if (collection == null) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - settings: RouteSettings(name: CollectionPage.routeName), - builder: (context) => CollectionPage(collection.derive(filter)), - ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), - ); - } } diff --git a/lib/widgets/stats/stats.dart b/lib/widgets/stats/stats.dart index 93183f133..797c2449d 100644 --- a/lib/widgets/stats/stats.dart +++ b/lib/widgets/stats/stats.dart @@ -8,6 +8,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/mime_types.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/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/collection/collection_page.dart'; @@ -25,14 +26,18 @@ import 'package:percent_indicator/linear_percent_indicator.dart'; class StatsPage extends StatelessWidget { static const routeName = '/collection/stats'; - final CollectionLens collection; + final CollectionSource source; + final CollectionLens parentCollection; final Map entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {}; - List get entries => collection.sortedEntries; + List get entries => parentCollection?.sortedEntries ?? source.rawEntries; static const mimeDonutMinWidth = 124.0; - StatsPage({this.collection}) { + StatsPage({ + @required this.source, + this.parentCollection, + }) : assert(source != null) { entries.forEach((entry) { if (entry.isLocated) { final address = entry.addressDetails; @@ -55,7 +60,7 @@ class StatsPage extends StatelessWidget { @override Widget build(BuildContext context) { Widget child; - if (collection.isEmpty) { + if (entries.isEmpty) { child = EmptyContent( icon: AIcons.image, text: 'No images', @@ -75,7 +80,7 @@ class StatsPage extends StatelessWidget { final catalogued = entries.where((entry) => entry.isCatalogued); final withGps = catalogued.where((entry) => entry.hasGps); - final withGpsPercent = withGps.length / collection.entryCount; + final withGpsPercent = withGps.length / entries.length; final textScaleFactor = MediaQuery.textScaleFactorOf(context); final lineHeight = 16 * textScaleFactor; final locationIndicator = Padding( @@ -105,9 +110,9 @@ class StatsPage extends StatelessWidget { children: [ mimeDonuts, locationIndicator, - ..._buildTopFilters('Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)), - ..._buildTopFilters('Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)), - ..._buildTopFilters('Top Tags', entryCountPerTag, (s) => TagFilter(s)), + ..._buildTopFilters(context, 'Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)), + ..._buildTopFilters(context, 'Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)), + ..._buildTopFilters(context, 'Top Tags', entryCountPerTag, (s) => TagFilter(s)), ], ); } @@ -178,7 +183,7 @@ class StatsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: seriesData .map((d) => GestureDetector( - onTap: () => _goToCollection(context, MimeFilter(d.mimeType)), + onTap: () => _onFilterSelection(context, MimeFilter(d.mimeType)), child: Text.rich( TextSpan( children: [ @@ -218,6 +223,7 @@ class StatsPage extends StatelessWidget { } List _buildTopFilters( + BuildContext context, String title, Map entryCountMap, CollectionFilter Function(String key) filterBuilder, @@ -233,22 +239,45 @@ class StatsPage extends StatelessWidget { ), ), FilterTable( - collection: collection, + totalEntryCount: entries.length, entryCountMap: entryCountMap, filterBuilder: filterBuilder, + onFilterSelection: (filter) => _onFilterSelection(context, filter), ), ]; } - void _goToCollection(BuildContext context, CollectionFilter filter) { - if (collection == null) return; + void _onFilterSelection(BuildContext context, CollectionFilter filter) { + if (parentCollection != null) { + _applyToParentCollectionPage(context, filter); + } else { + _jumpToCollectionPage(context, filter); + } + } + + void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) { + parentCollection.addFilter(filter); + // we post closing the search page after applying the filter selection + // so that hero animation target is ready in the `FilterBar`, + // even when the target is a child of an `AnimatedList` + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.pop(context); + }); + } + + void _jumpToCollectionPage(BuildContext context, CollectionFilter filter) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( settings: RouteSettings(name: CollectionPage.routeName), - builder: (context) => CollectionPage(collection.derive(filter)), + builder: (context) => CollectionPage(CollectionLens( + source: source, + filters: [filter], + groupFactor: settings.collectionGroupFactor, + sortFactor: settings.collectionSortFactor, + )), ), - settings.navRemoveRoutePredicate(CollectionPage.routeName), + (route) => false, ); } }