show entry in collection when leaving viewer

This commit is contained in:
Thibault Deckers 2021-06-10 17:30:03 +09:00
parent 7ac6617154
commit e9de80f887
3 changed files with 45 additions and 17 deletions

View file

@ -6,17 +6,22 @@ class HighlightInfo extends ChangeNotifier {
final EventBus eventBus = EventBus();
void trackItem<T>(
T item, {
T? item, {
TrackPredicate? predicate,
Alignment? alignment,
bool? animate,
Object? highlightItem,
}) =>
}) {
if (item != null) {
eventBus.fire(TrackEvent<T>(
item,
predicate ?? (_) => true,
alignment ?? Alignment.center,
animate ?? true,
highlightItem,
));
}
}
Object? _item;
@ -43,14 +48,20 @@ class HighlightInfo extends ChangeNotifier {
@immutable
class TrackEvent<T> {
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);

View file

@ -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<T, U extends StatefulWidget> on State<U>, WidgetsBind
void initState() {
super.initState();
final highlightInfo = context.read<HighlightInfo>();
_subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen((e) => _trackItem(
e.item,
alignment: e.alignment,
animate: e.animate,
highlightItem: e.highlightItem,
)));
_subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen(_trackItem));
_lastOrientation = _windowOrientation;
WidgetsBinding.instance!.addObserver(this);
_saveLayoutMetrics();
@ -66,22 +62,21 @@ mixin GridItemTrackerMixin<T, U extends StatefulWidget> on State<U>, 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<void> _trackItem(
T item, {
required Alignment alignment,
required bool animate,
required Object? highlightItem,
}) async {
Future<void> _trackItem(TrackEvent event) async {
final sectionedListLayout = context.read<SectionedListLayout<T>>();
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<T, U extends StatefulWidget> on State<U>, WidgetsBind
await Future.delayed(Durations.highlightJumpDelay);
}
final highlightItem = event.highlightItem;
if (highlightItem != null) {
context.read<HighlightInfo>().set(highlightItem);
}

View file

@ -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<EntryViewerStack> with SingleTickerPr
late EntryActionDelegate _actionDelegate;
final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = [];
final ValueNotifier<HeroInfo?> _heroInfoNotifier = ValueNotifier(null);
bool _isEntryTracked = true;
CollectionLens? get collection => widget.collection;
@ -166,6 +168,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> 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<EntryViewerStack> with SingleTickerPr
}
void _onVerticalPageControllerChange() {
if (!_isEntryTracked && _verticalPager.page?.floor() == transitionPage) {
_trackEntry();
}
_verticalScrollNotifier.notifyListeners();
}
@ -451,6 +457,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> 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<EntryViewerStack> 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<HighlightInfo>().trackItem(
entry,
predicate: (v) => v < 1,
animate: false,
);
}
}
void _onLeave() {
_showSystemUI();
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {