changed navigation history handling

This commit is contained in:
Thibault Deckers 2020-11-23 19:17:23 +09:00
parent ad2d9b3552
commit 5898c9052a
12 changed files with 75 additions and 85 deletions

View file

@ -198,14 +198,6 @@ class Settings extends ChangeNotifier {
set searchHistory(List<CollectionFilter> newValue) => setAndNotify(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList()); set searchHistory(List<CollectionFilter> 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 // convenience methods
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters

View file

@ -374,7 +374,8 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: StatsPage.routeName), settings: RouteSettings(name: StatsPage.routeName),
builder: (context) => StatsPage( builder: (context) => StatsPage(
collection: collection, source: source,
parentCollection: collection,
), ),
), ),
); );

View file

@ -111,7 +111,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
content, content,
widget.details, Flexible(child: widget.details),
], ],
); );
} }

View file

@ -51,7 +51,7 @@ class CollectionNavTile extends StatelessWidget {
sortFactor: settings.collectionSortFactor, sortFactor: settings.collectionSortFactor,
)), )),
), ),
settings.navRemoveRoutePredicate(CollectionPage.routeName), (route) => false,
); );
} }
} }

View file

@ -1,6 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/flutter_utils.dart'; import 'package:aves/utils/flutter_utils.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -52,7 +51,7 @@ class NavTile extends StatelessWidget {
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
route, route,
settings.navRemoveRoutePredicate(routeName), (route) => false,
); );
} else { } else {
Navigator.push(context, route); Navigator.push(context, route);

View file

@ -1,5 +1,4 @@
import 'package:aves/model/settings/settings.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/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/widgets/common/aves_selection_dialog.dart'; import 'package:aves/widgets/common/aves_selection_dialog.dart';
@ -52,11 +51,7 @@ abstract class ChipSetActionDelegate {
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: StatsPage.routeName), settings: RouteSettings(name: StatsPage.routeName),
builder: (context) => StatsPage( builder: (context) => StatsPage(
collection: CollectionLens( source: source,
source: source,
groupFactor: settings.collectionGroupFactor,
sortFactor: settings.collectionSortFactor,
),
), ),
), ),
); );

View file

@ -64,30 +64,21 @@ class DecoratedFilterChip extends StatelessWidget {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
AnimatedSwitcher( AnimatedCrossFade(
duration: Durations.chipDecorationAnimation, firstChild: Padding(
switchInCurve: Curves.easeInOutCubic, padding: EdgeInsets.only(right: 8),
switchOutCurve: Curves.easeInOutCubic, child: DecoratedIcon(
transitionBuilder: (child, animation) => FadeTransition( AIcons.pin,
opacity: animation, color: FilterGridPage.detailColor,
child: SizeTransition( shadows: [Constants.embossShadow],
axis: Axis.horizontal, size: 16,
sizeFactor: animation,
axisAlignment: 1.0,
child: child,
), ),
), ),
child: pinned secondChild: SizedBox.shrink(),
? Padding( sizeCurve: Curves.easeInOutCubic,
padding: EdgeInsets.only(right: 8), alignment: AlignmentDirectional.centerEnd,
child: DecoratedIcon( crossFadeState: pinned ? CrossFadeState.showFirst : CrossFadeState.showSecond,
AIcons.pin, duration: Durations.chipDecorationAnimation,
color: FilterGridPage.detailColor,
shadows: [Constants.embossShadow],
size: 16,
),
)
: SizedBox.shrink(),
), ),
if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)) if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album))
Padding( Padding(

View file

@ -76,7 +76,7 @@ class FilterNavigationPage extends StatelessWidget {
return sourceState != SourceState.loading && emptyBuilder != null ? emptyBuilder() : SizedBox.shrink(); return sourceState != SourceState.loading && emptyBuilder != null ? emptyBuilder() : SizedBox.shrink();
}, },
), ),
onTap: (filter) => Navigator.pushAndRemoveUntil( onTap: (filter) => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: CollectionPage.routeName), settings: RouteSettings(name: CollectionPage.routeName),
@ -87,7 +87,6 @@ class FilterNavigationPage extends StatelessWidget {
sortFactor: settings.collectionSortFactor, sortFactor: settings.collectionSortFactor,
)), )),
), ),
settings.navRemoveRoutePredicate(CollectionPage.routeName),
), ),
onLongPress: AvesApp.mode == AppMode.main ? (filter, tapPosition) => _showMenu(context, filter, tapPosition) : null, onLongPress: AvesApp.mode == AppMode.main ? (filter, tapPosition) => _showMenu(context, filter, tapPosition) : null,
); );

View file

@ -296,7 +296,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
settings: RouteSettings(name: CollectionPage.routeName), settings: RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(collection.derive(filter)), builder: (context) => CollectionPage(collection.derive(filter)),
), ),
settings.navRemoveRoutePredicate(CollectionPage.routeName), (route) => false,
); );
} }

View file

@ -22,8 +22,8 @@ import 'package:flutter/services.dart';
class ImageSearchDelegate { class ImageSearchDelegate {
final CollectionSource source; final CollectionSource source;
final ValueNotifier<String> expandedSectionNotifier = ValueNotifier(null);
final CollectionLens parentCollection; final CollectionLens parentCollection;
final ValueNotifier<String> expandedSectionNotifier = ValueNotifier(null);
static const searchHistoryCount = 10; static const searchHistoryCount = 10;
@ -188,14 +188,12 @@ class ImageSearchDelegate {
if (parentCollection != null) { if (parentCollection != null) {
_applyToParentCollectionPage(context, filter); _applyToParentCollectionPage(context, filter);
} else { } else {
_goToCollectionPage(context, filter); _jumpToCollectionPage(context, filter);
} }
} }
void _applyToParentCollectionPage(BuildContext context, CollectionFilter 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 // we post closing the search page after applying the filter selection
// so that hero animation target is ready in the `FilterBar`, // so that hero animation target is ready in the `FilterBar`,
// even when the target is a child of an `AnimatedList` // even when the target is a child of an `AnimatedList`
@ -209,7 +207,7 @@ class ImageSearchDelegate {
Navigator.pop(context); Navigator.pop(context);
} }
void _goToCollectionPage(BuildContext context, CollectionFilter filter) { void _jumpToCollectionPage(BuildContext context, CollectionFilter filter) {
_clean(); _clean();
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
@ -222,7 +220,7 @@ class ImageSearchDelegate {
sortFactor: settings.collectionSortFactor, sortFactor: settings.collectionSortFactor,
)), )),
), ),
settings.navRemoveRoutePredicate(CollectionPage.routeName), (route) => false,
); );
} }

View file

@ -1,9 +1,6 @@
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/source/collection_lens.dart';
import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/constants.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:aves/widgets/common/aves_filter_chip.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -11,14 +8,16 @@ import 'package:intl/intl.dart';
import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:percent_indicator/linear_percent_indicator.dart';
class FilterTable extends StatelessWidget { class FilterTable extends StatelessWidget {
final CollectionLens collection; final int totalEntryCount;
final Map<String, int> entryCountMap; final Map<String, int> entryCountMap;
final CollectionFilter Function(String key) filterBuilder; final CollectionFilter Function(String key) filterBuilder;
final FilterCallback onFilterSelection;
const FilterTable({ const FilterTable({
@required this.collection, @required this.totalEntryCount,
@required this.entryCountMap, @required this.entryCountMap,
@required this.filterBuilder, @required this.filterBuilder,
@required this.onFilterSelection,
}); });
static const chipWidth = AvesFilterChip.maxChipWidth; static const chipWidth = AvesFilterChip.maxChipWidth;
@ -27,7 +26,6 @@ class FilterTable extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final maxCount = collection.entryCount;
final sortedEntries = entryCountMap.entries.toList() final sortedEntries = entryCountMap.entries.toList()
..sort((kv1, kv2) { ..sort((kv1, kv2) {
final c = kv2.value.compareTo(kv1.value); final c = kv2.value.compareTo(kv1.value);
@ -47,7 +45,7 @@ class FilterTable extends StatelessWidget {
final filter = filterBuilder(kv.key); final filter = filterBuilder(kv.key);
final label = filter.label; final label = filter.label;
final count = kv.value; final count = kv.value;
final percent = count / maxCount; final percent = count / totalEntryCount;
return TableRow( return TableRow(
children: [ children: [
Container( Container(
@ -58,7 +56,7 @@ class FilterTable extends StatelessWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: AvesFilterChip( child: AvesFilterChip(
filter: filter, filter: filter,
onTap: (filter) => _goToCollection(context, filter), onTap: onFilterSelection,
), ),
), ),
if (showPercentIndicator) 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),
);
}
} }

View file

@ -8,6 +8,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/mime_types.dart'; import 'package:aves/model/mime_types.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.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/color_utils.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/collection_page.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 { class StatsPage extends StatelessWidget {
static const routeName = '/collection/stats'; static const routeName = '/collection/stats';
final CollectionLens collection; final CollectionSource source;
final CollectionLens parentCollection;
final Map<String, int> entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {}; final Map<String, int> entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {};
List<ImageEntry> get entries => collection.sortedEntries; List<ImageEntry> get entries => parentCollection?.sortedEntries ?? source.rawEntries;
static const mimeDonutMinWidth = 124.0; static const mimeDonutMinWidth = 124.0;
StatsPage({this.collection}) { StatsPage({
@required this.source,
this.parentCollection,
}) : assert(source != null) {
entries.forEach((entry) { entries.forEach((entry) {
if (entry.isLocated) { if (entry.isLocated) {
final address = entry.addressDetails; final address = entry.addressDetails;
@ -55,7 +60,7 @@ class StatsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child; Widget child;
if (collection.isEmpty) { if (entries.isEmpty) {
child = EmptyContent( child = EmptyContent(
icon: AIcons.image, icon: AIcons.image,
text: 'No images', text: 'No images',
@ -75,7 +80,7 @@ class StatsPage extends StatelessWidget {
final catalogued = entries.where((entry) => entry.isCatalogued); final catalogued = entries.where((entry) => entry.isCatalogued);
final withGps = catalogued.where((entry) => entry.hasGps); 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 textScaleFactor = MediaQuery.textScaleFactorOf(context);
final lineHeight = 16 * textScaleFactor; final lineHeight = 16 * textScaleFactor;
final locationIndicator = Padding( final locationIndicator = Padding(
@ -105,9 +110,9 @@ class StatsPage extends StatelessWidget {
children: [ children: [
mimeDonuts, mimeDonuts,
locationIndicator, locationIndicator,
..._buildTopFilters('Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)), ..._buildTopFilters(context, 'Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
..._buildTopFilters('Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)), ..._buildTopFilters(context, 'Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
..._buildTopFilters('Top Tags', entryCountPerTag, (s) => TagFilter(s)), ..._buildTopFilters(context, 'Top Tags', entryCountPerTag, (s) => TagFilter(s)),
], ],
); );
} }
@ -178,7 +183,7 @@ class StatsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: seriesData children: seriesData
.map((d) => GestureDetector( .map((d) => GestureDetector(
onTap: () => _goToCollection(context, MimeFilter(d.mimeType)), onTap: () => _onFilterSelection(context, MimeFilter(d.mimeType)),
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
children: [ children: [
@ -218,6 +223,7 @@ class StatsPage extends StatelessWidget {
} }
List<Widget> _buildTopFilters( List<Widget> _buildTopFilters(
BuildContext context,
String title, String title,
Map<String, int> entryCountMap, Map<String, int> entryCountMap,
CollectionFilter Function(String key) filterBuilder, CollectionFilter Function(String key) filterBuilder,
@ -233,22 +239,45 @@ class StatsPage extends StatelessWidget {
), ),
), ),
FilterTable( FilterTable(
collection: collection, totalEntryCount: entries.length,
entryCountMap: entryCountMap, entryCountMap: entryCountMap,
filterBuilder: filterBuilder, filterBuilder: filterBuilder,
onFilterSelection: (filter) => _onFilterSelection(context, filter),
), ),
]; ];
} }
void _goToCollection(BuildContext context, CollectionFilter filter) { void _onFilterSelection(BuildContext context, CollectionFilter filter) {
if (collection == null) return; 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( Navigator.pushAndRemoveUntil(
context, context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: CollectionPage.routeName), 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,
); );
} }
} }