show entry in collection when leaving viewer
This commit is contained in:
parent
7ac6617154
commit
e9de80f887
3 changed files with 45 additions and 17 deletions
|
@ -6,17 +6,22 @@ class HighlightInfo extends ChangeNotifier {
|
||||||
final EventBus eventBus = EventBus();
|
final EventBus eventBus = EventBus();
|
||||||
|
|
||||||
void trackItem<T>(
|
void trackItem<T>(
|
||||||
T item, {
|
T? item, {
|
||||||
|
TrackPredicate? predicate,
|
||||||
Alignment? alignment,
|
Alignment? alignment,
|
||||||
bool? animate,
|
bool? animate,
|
||||||
Object? highlightItem,
|
Object? highlightItem,
|
||||||
}) =>
|
}) {
|
||||||
|
if (item != null) {
|
||||||
eventBus.fire(TrackEvent<T>(
|
eventBus.fire(TrackEvent<T>(
|
||||||
item,
|
item,
|
||||||
|
predicate ?? (_) => true,
|
||||||
alignment ?? Alignment.center,
|
alignment ?? Alignment.center,
|
||||||
animate ?? true,
|
animate ?? true,
|
||||||
highlightItem,
|
highlightItem,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object? _item;
|
Object? _item;
|
||||||
|
|
||||||
|
@ -43,14 +48,20 @@ class HighlightInfo extends ChangeNotifier {
|
||||||
@immutable
|
@immutable
|
||||||
class TrackEvent<T> {
|
class TrackEvent<T> {
|
||||||
final T item;
|
final T item;
|
||||||
|
final TrackPredicate predicate;
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
final bool animate;
|
final bool animate;
|
||||||
final Object? highlightItem;
|
final Object? highlightItem;
|
||||||
|
|
||||||
const TrackEvent(
|
const TrackEvent(
|
||||||
this.item,
|
this.item,
|
||||||
|
this.predicate,
|
||||||
this.alignment,
|
this.alignment,
|
||||||
this.animate,
|
this.animate,
|
||||||
this.highlightItem,
|
this.highlightItem,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `itemVisibility`: percent of the item tracked already visible in viewport
|
||||||
|
// return whether to proceed with tracking
|
||||||
|
typedef TrackPredicate = bool Function(double itemVisibility);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
@ -36,12 +37,7 @@ mixin GridItemTrackerMixin<T, U extends StatefulWidget> on State<U>, WidgetsBind
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final highlightInfo = context.read<HighlightInfo>();
|
final highlightInfo = context.read<HighlightInfo>();
|
||||||
_subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen((e) => _trackItem(
|
_subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen(_trackItem));
|
||||||
e.item,
|
|
||||||
alignment: e.alignment,
|
|
||||||
animate: e.animate,
|
|
||||||
highlightItem: e.highlightItem,
|
|
||||||
)));
|
|
||||||
_lastOrientation = _windowOrientation;
|
_lastOrientation = _windowOrientation;
|
||||||
WidgetsBinding.instance!.addObserver(this);
|
WidgetsBinding.instance!.addObserver(this);
|
||||||
_saveLayoutMetrics();
|
_saveLayoutMetrics();
|
||||||
|
@ -66,22 +62,21 @@ mixin GridItemTrackerMixin<T, U extends StatefulWidget> on State<U>, WidgetsBind
|
||||||
// `Scrollable.ensureVisible` only works on already rendered objects
|
// `Scrollable.ensureVisible` only works on already rendered objects
|
||||||
// `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata`
|
// `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata`
|
||||||
// `RenderViewport.scrollOffsetOf` is a good alternative
|
// `RenderViewport.scrollOffsetOf` is a good alternative
|
||||||
Future<void> _trackItem(
|
Future<void> _trackItem(TrackEvent event) async {
|
||||||
T item, {
|
|
||||||
required Alignment alignment,
|
|
||||||
required bool animate,
|
|
||||||
required Object? highlightItem,
|
|
||||||
}) async {
|
|
||||||
final sectionedListLayout = context.read<SectionedListLayout<T>>();
|
final sectionedListLayout = context.read<SectionedListLayout<T>>();
|
||||||
final tileRect = sectionedListLayout.getTileRect(item);
|
final tileRect = sectionedListLayout.getTileRect(event.item);
|
||||||
if (tileRect == null) return;
|
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,
|
// most of the time the app bar will be scrolled away after scaling,
|
||||||
// so we compensate for it to center the focal point thumbnail
|
// so we compensate for it to center the focal point thumbnail
|
||||||
final appBarHeight = appBarHeightNotifier.value;
|
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) {
|
if (scrollOffset > 0) {
|
||||||
await scrollController.animateTo(
|
await scrollController.animateTo(
|
||||||
scrollOffset,
|
scrollOffset,
|
||||||
|
@ -95,6 +90,7 @@ mixin GridItemTrackerMixin<T, U extends StatefulWidget> on State<U>, WidgetsBind
|
||||||
await Future.delayed(Durations.highlightJumpDelay);
|
await Future.delayed(Durations.highlightJumpDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final highlightItem = event.highlightItem;
|
||||||
if (highlightItem != null) {
|
if (highlightItem != null) {
|
||||||
context.read<HighlightInfo>().set(highlightItem);
|
context.read<HighlightInfo>().set(highlightItem);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry_actions.dart';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/multipage.dart';
|
import 'package:aves/model/multipage.dart';
|
||||||
import 'package:aves/model/settings/enums.dart';
|
import 'package:aves/model/settings/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -62,6 +63,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
|
||||||
late EntryActionDelegate _actionDelegate;
|
late EntryActionDelegate _actionDelegate;
|
||||||
final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = [];
|
final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = [];
|
||||||
final ValueNotifier<HeroInfo?> _heroInfoNotifier = ValueNotifier(null);
|
final ValueNotifier<HeroInfo?> _heroInfoNotifier = ValueNotifier(null);
|
||||||
|
bool _isEntryTracked = true;
|
||||||
|
|
||||||
CollectionLens? get collection => widget.collection;
|
CollectionLens? get collection => widget.collection;
|
||||||
|
|
||||||
|
@ -166,6 +168,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
|
||||||
// back from info to image
|
// back from info to image
|
||||||
_goToVerticalPage(imagePage);
|
_goToVerticalPage(imagePage);
|
||||||
} else {
|
} else {
|
||||||
|
if (!_isEntryTracked) _trackEntry();
|
||||||
_popVisual();
|
_popVisual();
|
||||||
}
|
}
|
||||||
return SynchronousFuture(false);
|
return SynchronousFuture(false);
|
||||||
|
@ -370,6 +373,9 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVerticalPageControllerChange() {
|
void _onVerticalPageControllerChange() {
|
||||||
|
if (!_isEntryTracked && _verticalPager.page?.floor() == transitionPage) {
|
||||||
|
_trackEntry();
|
||||||
|
}
|
||||||
_verticalScrollNotifier.notifyListeners();
|
_verticalScrollNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +457,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
|
||||||
final newEntry = _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null;
|
final newEntry = _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null;
|
||||||
if (_entryNotifier.value == newEntry) return;
|
if (_entryNotifier.value == newEntry) return;
|
||||||
_entryNotifier.value = newEntry;
|
_entryNotifier.value = newEntry;
|
||||||
|
_isEntryTracked = false;
|
||||||
await _pauseVideoControllers();
|
await _pauseVideoControllers();
|
||||||
await _initEntryControllers();
|
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() {
|
void _onLeave() {
|
||||||
_showSystemUI();
|
_showSystemUI();
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
|
|
Loading…
Reference in a new issue