diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 68275752a..6d4f5bc74 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -21,6 +21,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel EntryGroupFactor groupFactor; EntrySortFactor sortFactor; final AChangeNotifier filterChangeNotifier = AChangeNotifier(); + int id; bool listenToSource; List _filteredEntries; @@ -33,10 +34,12 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel Iterable filters, @required EntryGroupFactor groupFactor, @required EntrySortFactor sortFactor, + this.id, this.listenToSource = true, }) : filters = {if (filters != null) ...filters.where((f) => f != null)}, groupFactor = groupFactor ?? EntryGroupFactor.month, sortFactor = sortFactor ?? EntrySortFactor.date { + id ??= hashCode; if (listenToSource) { _subscriptions.add(source.eventBus.on().listen((e) => _refresh())); _subscriptions.add(source.eventBus.on().listen((e) => onEntryRemoved(e.entries))); diff --git a/lib/widgets/collection/grid/thumbnail.dart b/lib/widgets/collection/grid/thumbnail.dart index 3eabb767c..d6ddf27bd 100644 --- a/lib/widgets/collection/grid/thumbnail.dart +++ b/lib/widgets/collection/grid/thumbnail.dart @@ -54,16 +54,20 @@ class InteractiveThumbnail extends StatelessWidget { context, TransparentMaterialPageRoute( settings: RouteSettings(name: EntryViewerPage.routeName), - pageBuilder: (c, a, sa) => EntryViewerPage( - collection: CollectionLens( + pageBuilder: (c, a, sa) { + final viewerCollection = CollectionLens( source: collection.source, filters: collection.filters, groupFactor: collection.groupFactor, sortFactor: collection.sortFactor, + id: collection.id, listenToSource: false, - ), - initialEntry: entry, - ), + ); + return EntryViewerPage( + collection: viewerCollection, + initialEntry: entry, + ); + }, ), ); } diff --git a/lib/widgets/collection/thumbnail/decorated.dart b/lib/widgets/collection/thumbnail/decorated.dart index 2920c100c..938e30a34 100644 --- a/lib/widgets/collection/thumbnail/decorated.dart +++ b/lib/widgets/collection/thumbnail/decorated.dart @@ -27,17 +27,21 @@ class DecoratedThumbnail extends StatelessWidget { @override Widget build(BuildContext context) { + // hero tag should include a collection identifier, so that it animates + // between different views of the entry in the same collection (e.g. thumbnails <-> viewer) + // but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer) + final heroTag = hashValues(collection?.id, entry); var child = entry.isSvg ? VectorImageThumbnail( entry: entry, extent: extent, - canHero: true, + heroTag: heroTag, ) : RasterImageThumbnail( entry: entry, extent: extent, isScrollingNotifier: isScrollingNotifier, - canHero: true, + heroTag: heroTag, ); child = Stack( diff --git a/lib/widgets/collection/thumbnail/raster.dart b/lib/widgets/collection/thumbnail/raster.dart index a67caeb4d..006e83480 100644 --- a/lib/widgets/collection/thumbnail/raster.dart +++ b/lib/widgets/collection/thumbnail/raster.dart @@ -10,14 +10,14 @@ class RasterImageThumbnail extends StatefulWidget { final AvesEntry entry; final double extent; final ValueNotifier isScrollingNotifier; - final bool canHero; + final Object heroTag; const RasterImageThumbnail({ Key key, @required this.entry, @required this.extent, this.isScrollingNotifier, - this.canHero = false, + this.heroTag, }) : super(key: key); @override @@ -124,9 +124,9 @@ class _RasterImageThumbnailState extends State { height: extent, fit: BoxFit.cover, ); - return widget.canHero + return widget.heroTag != null ? Hero( - tag: entry, + tag: widget.heroTag, flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { return TransitionImage( image: entry.getBestThumbnail(extent), diff --git a/lib/widgets/collection/thumbnail/vector.dart b/lib/widgets/collection/thumbnail/vector.dart index c2cb919ed..d9d379ff5 100644 --- a/lib/widgets/collection/thumbnail/vector.dart +++ b/lib/widgets/collection/thumbnail/vector.dart @@ -10,13 +10,13 @@ import 'package:provider/provider.dart'; class VectorImageThumbnail extends StatelessWidget { final AvesEntry entry; final double extent; - final bool canHero; + final Object heroTag; const VectorImageThumbnail({ Key key, @required this.entry, @required this.extent, - this.canHero = false, + this.heroTag, }) : super(key: key); @override @@ -63,9 +63,9 @@ class VectorImageThumbnail extends StatelessWidget { ); }, ); - return canHero + return heroTag != null ? Hero( - tag: entry, + tag: heroTag, transitionOnUserGestures: true, child: child, ) diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 405817f62..8b1d070b1 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -77,7 +77,7 @@ class _EntryViewerStackState extends State with SingleTickerPr super.initState(); final entry = widget.initialEntry; // opening hero, with viewer as target - _heroInfoNotifier.value = HeroInfo(entry); + _heroInfoNotifier.value = HeroInfo(collection?.id, entry); _entryNotifier.value = entry; _currentHorizontalPage = max(0, entries.indexOf(entry)); _currentVerticalPage = ValueNotifier(imagePage); @@ -421,7 +421,7 @@ class _EntryViewerStackState extends State with SingleTickerPr } // closing hero, with viewer as source - final heroInfo = HeroInfo(_entryNotifier.value); + final heroInfo = HeroInfo(collection?.id, _entryNotifier.value); if (_heroInfoNotifier.value != heroInfo) { _heroInfoNotifier.value = heroInfo; // we post closing the viewer page so that hero animation source is ready diff --git a/lib/widgets/viewer/hero.dart b/lib/widgets/viewer/hero.dart index c7db1594c..80fa947fa 100644 --- a/lib/widgets/viewer/hero.dart +++ b/lib/widgets/viewer/hero.dart @@ -1,16 +1,21 @@ import 'package:aves/model/entry.dart'; +import 'package:flutter/widgets.dart'; class HeroInfo { + // hero tag should include a collection identifier, so that it animates + // between different views of the entry in the same collection (e.g. thumbnails <-> viewer) + // but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer) + final int collectionId; final AvesEntry entry; - const HeroInfo(this.entry); + const HeroInfo(this.collectionId, this.entry); @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - return other is HeroInfo && other.entry == entry; + return other is HeroInfo && other.collectionId == collectionId && other.entry == entry; } @override - int get hashCode => entry.hashCode; + int get hashCode => hashValues(collectionId, entry); } diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index d822de4ce..1ba5e27d5 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -145,7 +145,7 @@ class _EntryPageViewState extends State { return Consumer( builder: (context, info, child) => Hero( - tag: info?.entry == mainEntry ? mainEntry : hashCode, + tag: info?.entry == mainEntry ? hashValues(info.collectionId, mainEntry) : hashCode, transitionOnUserGestures: true, child: child, ),