#539 viewer: stiffer spring physics for vertical pager
This commit is contained in:
parent
50b4006352
commit
3306bc6340
6 changed files with 51 additions and 21 deletions
|
@ -84,7 +84,7 @@ class DurationsData {
|
||||||
final Duration tvImageFocusAnimation;
|
final Duration tvImageFocusAnimation;
|
||||||
|
|
||||||
// viewer animations
|
// viewer animations
|
||||||
final Duration viewerVerticalPageScrollAnimation;
|
final Duration viewerHorizontalPageScrollAnimation;
|
||||||
final Duration viewerOverlayAnimation;
|
final Duration viewerOverlayAnimation;
|
||||||
final Duration viewerOverlayChangeAnimation;
|
final Duration viewerOverlayChangeAnimation;
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class DurationsData {
|
||||||
this.staggeredAnimationPageTarget = const Duration(milliseconds: 800),
|
this.staggeredAnimationPageTarget = const Duration(milliseconds: 800),
|
||||||
this.quickChooserAnimation = const Duration(milliseconds: 100),
|
this.quickChooserAnimation = const Duration(milliseconds: 100),
|
||||||
this.tvImageFocusAnimation = const Duration(milliseconds: 150),
|
this.tvImageFocusAnimation = const Duration(milliseconds: 150),
|
||||||
this.viewerVerticalPageScrollAnimation = const Duration(milliseconds: 500),
|
this.viewerHorizontalPageScrollAnimation = const Duration(milliseconds: 400),
|
||||||
this.viewerOverlayAnimation = const Duration(milliseconds: 200),
|
this.viewerOverlayAnimation = const Duration(milliseconds: 200),
|
||||||
this.viewerOverlayChangeAnimation = const Duration(milliseconds: 150),
|
this.viewerOverlayChangeAnimation = const Duration(milliseconds: 150),
|
||||||
}) : staggeredAnimationDelay = staggeredAnimation ~/ 6;
|
}) : staggeredAnimationDelay = staggeredAnimation ~/ 6;
|
||||||
|
@ -120,7 +120,7 @@ class DurationsData {
|
||||||
staggeredAnimationPageTarget: Duration.zero,
|
staggeredAnimationPageTarget: Duration.zero,
|
||||||
quickChooserAnimation: Duration.zero,
|
quickChooserAnimation: Duration.zero,
|
||||||
tvImageFocusAnimation: Duration.zero,
|
tvImageFocusAnimation: Duration.zero,
|
||||||
viewerVerticalPageScrollAnimation: Duration.zero,
|
viewerHorizontalPageScrollAnimation: Duration.zero,
|
||||||
viewerOverlayAnimation: Duration.zero,
|
viewerOverlayAnimation: Duration.zero,
|
||||||
viewerOverlayChangeAnimation: Duration.zero,
|
viewerOverlayChangeAnimation: Duration.zero,
|
||||||
);
|
);
|
||||||
|
|
20
lib/widgets/common/behaviour/springy_scroll_physics.dart
Normal file
20
lib/widgets/common/behaviour/springy_scroll_physics.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class SpringyScrollPhysics extends ScrollPhysics {
|
||||||
|
@override
|
||||||
|
final SpringDescription spring;
|
||||||
|
|
||||||
|
const SpringyScrollPhysics({
|
||||||
|
required this.spring,
|
||||||
|
super.parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SpringyScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||||
|
return SpringyScrollPhysics(
|
||||||
|
spring: spring,
|
||||||
|
parent: buildParent(ancestor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
import 'package:aves/widgets/common/behaviour/springy_scroll_physics.dart';
|
||||||
import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/controller.dart';
|
import 'package:aves/widgets/viewer/controls/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/intents.dart';
|
import 'package:aves/widgets/viewer/controls/intents.dart';
|
||||||
|
@ -37,6 +38,13 @@ class ViewerVerticalPageView extends StatefulWidget {
|
||||||
final VoidCallback onImagePageRequested;
|
final VoidCallback onImagePageRequested;
|
||||||
final void Function(AvesEntry mainEntry, AvesEntry? pageEntry) onViewDisposed;
|
final void Function(AvesEntry mainEntry, AvesEntry? pageEntry) onViewDisposed;
|
||||||
|
|
||||||
|
// critically damped spring a bit stiffer than `ScrollPhysics._kDefaultSpring`
|
||||||
|
static final spring = SpringDescription.withDampingRatio(
|
||||||
|
mass: 0.5,
|
||||||
|
stiffness: 200.0,
|
||||||
|
ratio: 1.0,
|
||||||
|
);
|
||||||
|
|
||||||
const ViewerVerticalPageView({
|
const ViewerVerticalPageView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.collection,
|
required this.collection,
|
||||||
|
@ -180,7 +188,9 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
controller: widget.verticalPager,
|
controller: widget.verticalPager,
|
||||||
physics: MagnifierScrollerPhysics(
|
physics: MagnifierScrollerPhysics(
|
||||||
gestureSettings: context.select<MediaQueryData, DeviceGestureSettings>((mq) => mq.gestureSettings),
|
gestureSettings: context.select<MediaQueryData, DeviceGestureSettings>((mq) => mq.gestureSettings),
|
||||||
parent: const PageScrollPhysics(),
|
parent: SpringyScrollPhysics(
|
||||||
|
spring: ViewerVerticalPageView.spring,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPageChanged: widget.onVerticalPageChanged,
|
onPageChanged: widget.onVerticalPageChanged,
|
||||||
children: pages,
|
children: pages,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_timeout.dart';
|
import 'package:aves/model/settings/enums/accessibility_timeout.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
|
@ -62,7 +63,7 @@ class EntryViewerStack extends StatefulWidget {
|
||||||
State<EntryViewerStack> createState() => _EntryViewerStackState();
|
State<EntryViewerStack> createState() => _EntryViewerStackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewControllerMixin, FeedbackMixin, SingleTickerProviderStateMixin {
|
class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewControllerMixin, FeedbackMixin, TickerProviderStateMixin {
|
||||||
final Floating _floating = Floating();
|
final Floating _floating = Floating();
|
||||||
late int _currentEntryIndex;
|
late int _currentEntryIndex;
|
||||||
late ValueNotifier<int> _currentVerticalPage;
|
late ValueNotifier<int> _currentVerticalPage;
|
||||||
|
@ -72,7 +73,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
final ValueNotifier<bool> _viewLocked = ValueNotifier(false);
|
final ValueNotifier<bool> _viewLocked = ValueNotifier(false);
|
||||||
final ValueNotifier<bool> _overlayExpandedNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _overlayExpandedNotifier = ValueNotifier(false);
|
||||||
late AnimationController _overlayAnimationController;
|
late AnimationController _verticalPageAnimationController, _overlayAnimationController;
|
||||||
late Animation<double> _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
|
late Animation<double> _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
|
||||||
late Animation<Offset> _overlayTopOffset;
|
late Animation<Offset> _overlayTopOffset;
|
||||||
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
||||||
|
@ -125,6 +126,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
_currentVerticalPage = ValueNotifier(imagePage);
|
_currentVerticalPage = ValueNotifier(imagePage);
|
||||||
_horizontalPager = PageController(initialPage: _currentEntryIndex);
|
_horizontalPager = PageController(initialPage: _currentEntryIndex);
|
||||||
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChanged);
|
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChanged);
|
||||||
|
_verticalPageAnimationController = AnimationController.unbounded(vsync: this);
|
||||||
|
_verticalPageAnimationController.addListener(() => _verticalPager.jumpTo(_verticalPageAnimationController.value));
|
||||||
_overlayAnimationController = AnimationController(
|
_overlayAnimationController = AnimationController(
|
||||||
duration: context.read<DurationsData>().viewerOverlayAnimation,
|
duration: context.read<DurationsData>().viewerOverlayAnimation,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
@ -171,6 +174,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
_floating.dispose();
|
_floating.dispose();
|
||||||
cleanEntryControllers(entryNotifier.value);
|
cleanEntryControllers(entryNotifier.value);
|
||||||
_videoActionDelegate.dispose();
|
_videoActionDelegate.dispose();
|
||||||
|
_verticalPageAnimationController.dispose();
|
||||||
_overlayAnimationController.dispose();
|
_overlayAnimationController.dispose();
|
||||||
_overlayVisible.dispose();
|
_overlayVisible.dispose();
|
||||||
_viewLocked.dispose();
|
_viewLocked.dispose();
|
||||||
|
@ -606,14 +610,11 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _goToVerticalPage(int page) async {
|
Future<void> _goToVerticalPage(int page) async {
|
||||||
final animationDuration = context.read<DurationsData>().viewerVerticalPageScrollAnimation;
|
if (settings.accessibilityAnimations.animate) {
|
||||||
if (animationDuration > Duration.zero) {
|
final start = _verticalPager.offset;
|
||||||
// duration & curve should feel similar to changing page by vertical fling
|
final end = _verticalPager.position.viewportDimension * page;
|
||||||
await _verticalPager.animateToPage(
|
final simulation = ScrollSpringSimulation(ViewerVerticalPageView.spring, start, end, 0);
|
||||||
page,
|
unawaited(_verticalPageAnimationController.animateWith(simulation));
|
||||||
duration: animationDuration,
|
|
||||||
curve: Curves.easeOutQuart,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
_verticalPager.jumpToPage(page);
|
_verticalPager.jumpToPage(page);
|
||||||
}
|
}
|
||||||
|
@ -653,13 +654,13 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
page = page.clamp(0, _collection.entryCount - 1);
|
page = page.clamp(0, _collection.entryCount - 1);
|
||||||
}
|
}
|
||||||
if (_currentEntryIndex != page) {
|
if (_currentEntryIndex != page) {
|
||||||
final animationDuration = animate ? context.read<DurationsData>().viewerVerticalPageScrollAnimation : Duration.zero;
|
final animationDuration = animate ? context.read<DurationsData>().viewerHorizontalPageScrollAnimation : Duration.zero;
|
||||||
if (animationDuration > Duration.zero) {
|
if (animationDuration > Duration.zero) {
|
||||||
// duration & curve should feel similar to changing page by vertical fling
|
// duration & curve should feel similar to changing page by fling
|
||||||
await _horizontalPager.animateToPage(
|
await _horizontalPager.animateToPage(
|
||||||
page,
|
page,
|
||||||
duration: animationDuration,
|
duration: animationDuration,
|
||||||
curve: Curves.easeOutQuart,
|
curve: Curves.easeOutCubic,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_horizontalPager.jumpToPage(page);
|
_horizontalPager.jumpToPage(page);
|
||||||
|
|
|
@ -124,11 +124,10 @@ class _InfoPageState extends State<InfoPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToViewer() {
|
void _goToViewer() {
|
||||||
final animationDuration = context.read<DurationsData>().viewerVerticalPageScrollAnimation;
|
|
||||||
ShowImageNotification().dispatch(context);
|
ShowImageNotification().dispatch(context);
|
||||||
_scrollController.animateTo(
|
_scrollController.animateTo(
|
||||||
0,
|
0,
|
||||||
duration: animationDuration,
|
duration: Durations.pageTransitionAnimation,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ class MagnifierScrollerPhysics extends ScrollPhysics {
|
||||||
const MagnifierScrollerPhysics({
|
const MagnifierScrollerPhysics({
|
||||||
required this.gestureSettings,
|
required this.gestureSettings,
|
||||||
this.touchSlopFactor = 1,
|
this.touchSlopFactor = 1,
|
||||||
ScrollPhysics? parent,
|
super.parent,
|
||||||
}) : super(parent: parent);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MagnifierScrollerPhysics applyTo(ScrollPhysics? ancestor) {
|
MagnifierScrollerPhysics applyTo(ScrollPhysics? ancestor) {
|
||||||
|
|
Loading…
Reference in a new issue