entry hero review
This commit is contained in:
parent
535d4c0d00
commit
ba0d91a1ff
6 changed files with 23 additions and 19 deletions
|
@ -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]),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!,
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue