From e9de80f8877b760d60a2b4a93a8d88093101e393 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 10 Jun 2021 17:30:03 +0900 Subject: [PATCH] show entry in collection when leaving viewer --- lib/model/highlight.dart | 15 +++++++++++-- lib/widgets/common/grid/item_tracker.dart | 26 +++++++++------------- lib/widgets/viewer/entry_viewer_stack.dart | 21 +++++++++++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/lib/model/highlight.dart b/lib/model/highlight.dart index 439283b77..642abe8ac 100644 --- a/lib/model/highlight.dart +++ b/lib/model/highlight.dart @@ -6,17 +6,22 @@ class HighlightInfo extends ChangeNotifier { final EventBus eventBus = EventBus(); void trackItem( - T item, { + T? item, { + TrackPredicate? predicate, Alignment? alignment, bool? animate, Object? highlightItem, - }) => + }) { + if (item != null) { eventBus.fire(TrackEvent( item, + predicate ?? (_) => true, alignment ?? Alignment.center, animate ?? true, highlightItem, )); + } + } Object? _item; @@ -43,14 +48,20 @@ class HighlightInfo extends ChangeNotifier { @immutable class TrackEvent { final T item; + final TrackPredicate predicate; final Alignment alignment; final bool animate; final Object? highlightItem; const TrackEvent( this.item, + this.predicate, this.alignment, this.animate, this.highlightItem, ); } + +// `itemVisibility`: percent of the item tracked already visible in viewport +// return whether to proceed with tracking +typedef TrackPredicate = bool Function(double itemVisibility); diff --git a/lib/widgets/common/grid/item_tracker.dart b/lib/widgets/common/grid/item_tracker.dart index addd775b9..d9d16b3a5 100644 --- a/lib/widgets/common/grid/item_tracker.dart +++ b/lib/widgets/common/grid/item_tracker.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:aves/model/highlight.dart'; import 'package:aves/theme/durations.dart'; @@ -36,12 +37,7 @@ mixin GridItemTrackerMixin on State, WidgetsBind void initState() { super.initState(); final highlightInfo = context.read(); - _subscriptions.add(highlightInfo.eventBus.on>().listen((e) => _trackItem( - e.item, - alignment: e.alignment, - animate: e.animate, - highlightItem: e.highlightItem, - ))); + _subscriptions.add(highlightInfo.eventBus.on>().listen(_trackItem)); _lastOrientation = _windowOrientation; WidgetsBinding.instance!.addObserver(this); _saveLayoutMetrics(); @@ -66,22 +62,21 @@ mixin GridItemTrackerMixin on State, WidgetsBind // `Scrollable.ensureVisible` only works on already rendered objects // `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata` // `RenderViewport.scrollOffsetOf` is a good alternative - Future _trackItem( - T item, { - required Alignment alignment, - required bool animate, - required Object? highlightItem, - }) async { + Future _trackItem(TrackEvent event) async { final sectionedListLayout = context.read>(); - final tileRect = sectionedListLayout.getTileRect(item); + final tileRect = sectionedListLayout.getTileRect(event.item); if (tileRect == null) return; + final viewportRect = Rect.fromLTWH(0, scrollController.offset, scrollableSize.width, scrollableSize.height); + final itemVisibility = max(0, tileRect.intersect(viewportRect).height) / tileRect.height; + if (!event.predicate(itemVisibility)) return; + // most of the time the app bar will be scrolled away after scaling, // so we compensate for it to center the focal point thumbnail final appBarHeight = appBarHeightNotifier.value; - final scrollOffset = appBarHeight + tileRect.top + (tileRect.height - scrollableSize.height) * ((alignment.y + 1) / 2); + final scrollOffset = appBarHeight + tileRect.top + (tileRect.height - scrollableSize.height) * ((event.alignment.y + 1) / 2); - if (animate) { + if (event.animate) { if (scrollOffset > 0) { await scrollController.animateTo( scrollOffset, @@ -95,6 +90,7 @@ mixin GridItemTrackerMixin on State, WidgetsBind await Future.delayed(Durations.highlightJumpDelay); } + final highlightItem = event.highlightItem; if (highlightItem != null) { context.read().set(highlightItem); } diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 047846027..3a404382e 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/highlight.dart'; import 'package:aves/model/multipage.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; @@ -62,6 +63,7 @@ class _EntryViewerStackState extends State with SingleTickerPr late EntryActionDelegate _actionDelegate; final List>> _viewStateNotifiers = []; final ValueNotifier _heroInfoNotifier = ValueNotifier(null); + bool _isEntryTracked = true; CollectionLens? get collection => widget.collection; @@ -166,6 +168,7 @@ class _EntryViewerStackState extends State with SingleTickerPr // back from info to image _goToVerticalPage(imagePage); } else { + if (!_isEntryTracked) _trackEntry(); _popVisual(); } return SynchronousFuture(false); @@ -370,6 +373,9 @@ class _EntryViewerStackState extends State with SingleTickerPr } void _onVerticalPageControllerChange() { + if (!_isEntryTracked && _verticalPager.page?.floor() == transitionPage) { + _trackEntry(); + } _verticalScrollNotifier.notifyListeners(); } @@ -451,6 +457,7 @@ class _EntryViewerStackState extends State with SingleTickerPr final newEntry = _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; if (_entryNotifier.value == newEntry) return; _entryNotifier.value = newEntry; + _isEntryTracked = false; await _pauseVideoControllers(); await _initEntryControllers(); } @@ -478,6 +485,20 @@ class _EntryViewerStackState extends State with SingleTickerPr } } + // track item when returning to collection, + // if they are not fully visible already + void _trackEntry() { + _isEntryTracked = true; + final entry = _entryNotifier.value; + if (entry != null && hasCollection) { + context.read().trackItem( + entry, + predicate: (v) => v < 1, + animate: false, + ); + } + } + void _onLeave() { _showSystemUI(); if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {