entry hero review

This commit is contained in:
Thibault Deckers 2024-09-14 00:47:15 +02:00
parent 535d4c0d00
commit ba0d91a1ff
6 changed files with 23 additions and 19 deletions

View file

@ -8,6 +8,7 @@ import 'package:aves/widgets/collection/grid/list_details_theme.dart';
import 'package:aves/widgets/common/grid/scaling.dart'; import 'package:aves/widgets/common/grid/scaling.dart';
import 'package:aves/widgets/common/thumbnail/decorated.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart';
import 'package:aves/widgets/common/thumbnail/notifications.dart'; import 'package:aves/widgets/common/thumbnail/notifications.dart';
import 'package:aves/widgets/viewer/hero.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -62,10 +63,7 @@ class InteractiveTile extends StatelessWidget {
selectable: true, selectable: true,
highlightable: true, highlightable: true,
isScrollingNotifier: isScrollingNotifier, isScrollingNotifier: isScrollingNotifier,
// hero tag should include a collection identifier, so that it animates heroTagger: () => EntryHeroInfo(collection, entry).tag,
// 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)
heroTagger: () => Object.hashAll([collection.id, entry.id]),
), ),
), ),
); );

View file

@ -261,11 +261,12 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
}, },
); );
if (animate && widget.heroTag != null) { final heroTag = widget.heroTag;
if (animate && heroTag != null) {
final background = settings.imageBackground; final background = settings.imageBackground;
final backgroundColor = background.isColor ? background.color : null; final backgroundColor = background.isColor ? background.color : null;
image = Hero( image = Hero(
tag: widget.heroTag!, tag: heroTag,
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
Widget child = TransitionImage( Widget child = TransitionImage(
image: entry.bestCachedThumbnail, image: entry.bestCachedThumbnail,
@ -296,9 +297,10 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
extent: extent, extent: extent,
); );
if (animate && widget.heroTag != null) { final heroTag = widget.heroTag;
if (animate && heroTag != null) {
child = Hero( child = Hero(
tag: widget.heroTag!, tag: heroTag,
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: DefaultTextStyle( child: DefaultTextStyle(

View file

@ -6,6 +6,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/common/thumbnail/scroller.dart'; import 'package:aves/widgets/common/thumbnail/scroller.dart';
import 'package:aves/widgets/map/info_row.dart'; import 'package:aves/widgets/map/info_row.dart';
import 'package:aves/widgets/viewer/hero.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MapEntryScroller extends StatefulWidget { class MapEntryScroller extends StatefulWidget {
@ -85,7 +86,7 @@ class _MapEntryScrollerState extends State<MapEntryScroller> {
entryBuilder: (index) => index < regionEntries.length ? regionEntries[index] : null, entryBuilder: (index) => index < regionEntries.length ? regionEntries[index] : null,
indexNotifier: widget.selectedIndexNotifier, indexNotifier: widget.selectedIndexNotifier,
onTap: widget.onTap, onTap: widget.onTap,
heroTagger: (entry) => Object.hashAll([regionCollection?.id, entry.id]), heroTagger: (entry) => EntryHeroInfo(regionCollection, entry).tag,
highlightable: true, highlightable: true,
showLocation: false, showLocation: false,
); );

View file

@ -75,7 +75,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
late Animation<Offset> _overlayTopOffset; late Animation<Offset> _overlayTopOffset;
EdgeInsets? _frozenViewInsets, _frozenViewPadding; EdgeInsets? _frozenViewInsets, _frozenViewPadding;
late VideoActionDelegate _videoActionDelegate; late VideoActionDelegate _videoActionDelegate;
final ValueNotifier<HeroInfo?> _heroInfoNotifier = ValueNotifier(null); final ValueNotifier<EntryHeroInfo?> _heroInfoNotifier = ValueNotifier(null);
bool _isEntryTracked = true; bool _isEntryTracked = true;
Timer? _overlayHidingTimer, _appInactiveReactionTimer; Timer? _overlayHidingTimer, _appInactiveReactionTimer;
@ -116,7 +116,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final initialEntry = widget.initialEntry; final initialEntry = widget.initialEntry;
final entry = entries.firstWhereOrNull((entry) => entry.id == initialEntry.id) ?? entries.firstOrNull; final entry = entries.firstWhereOrNull((entry) => entry.id == initialEntry.id) ?? entries.firstOrNull;
// opening hero, with viewer as target // opening hero, with viewer as target
_heroInfoNotifier.value = HeroInfo(collection?.id, entry); _heroInfoNotifier.value = EntryHeroInfo(collection, entry);
entryNotifier = viewerController.entryNotifier; entryNotifier = viewerController.entryNotifier;
entryNotifier.value = entry; entryNotifier.value = entry;
_currentEntryIndex = max(0, entry != null ? entries.indexOf(entry) : -1); _currentEntryIndex = max(0, entry != null ? entries.indexOf(entry) : -1);
@ -224,7 +224,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
_onPopInvoked(); _onPopInvoked();
}, },
child: ValueListenableProvider<HeroInfo?>.value( child: ValueListenableProvider<EntryHeroInfo?>.value(
value: _heroInfoNotifier, value: _heroInfoNotifier,
child: NotificationListener( child: NotificationListener(
onNotification: _handleNotification, onNotification: _handleNotification,
@ -867,7 +867,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
// closing hero, with viewer as source // closing hero, with viewer as source
final heroInfo = HeroInfo(collection?.id, entryNotifier.value); final heroInfo = EntryHeroInfo(collection, entryNotifier.value);
if (_heroInfoNotifier.value != heroInfo) { if (_heroInfoNotifier.value != heroInfo) {
_heroInfoNotifier.value = heroInfo; _heroInfoNotifier.value = heroInfo;
// we post closing the viewer page so that hero animation source is ready // we post closing the viewer page so that hero animation source is ready

View file

@ -1,17 +1,20 @@
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@immutable @immutable
class HeroInfo extends Equatable { class EntryHeroInfo extends Equatable {
// hero tag should include a collection identifier, so that it animates // 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) // 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) // but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer)
final int? collectionId; final CollectionLens? collection;
final AvesEntry? entry; final AvesEntry? entry;
@override @override
List<Object?> get props => [collectionId, entry?.uri]; List<Object?> get props => [collection?.id, entry?.uri];
const HeroInfo(this.collectionId, this.entry); const EntryHeroInfo(this.collection, this.entry);
int get tag => Object.hashAll([collection?.id, entry?.uri]);
} }

View file

@ -150,9 +150,9 @@ class _EntryPageViewState extends State<EntryPageView> with TickerProviderStateM
final animate = context.select<Settings, bool>((v) => v.animate); final animate = context.select<Settings, bool>((v) => v.animate);
if (animate) { if (animate) {
child = Consumer<HeroInfo?>( child = Consumer<EntryHeroInfo?>(
builder: (context, info, child) => Hero( builder: (context, info, child) => Hero(
tag: info != null && info.entry == mainEntry ? Object.hashAll([info.collectionId, mainEntry.id]) : hashCode, tag: info != null && info.entry == mainEntry ? info.tag : hashCode,
transitionOnUserGestures: true, transitionOnUserGestures: true,
child: child!, child: child!,
), ),