From 3306bc6340062b090452db87bde2e1e6d8c62a2a Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 29 Apr 2023 15:31:35 +0200 Subject: [PATCH] #539 viewer: stiffer spring physics for vertical pager --- lib/theme/durations.dart | 6 ++--- .../behaviour/springy_scroll_physics.dart | 20 ++++++++++++++ lib/widgets/viewer/entry_vertical_pager.dart | 12 ++++++++- lib/widgets/viewer/entry_viewer_stack.dart | 27 ++++++++++--------- lib/widgets/viewer/info/info_page.dart | 3 +-- .../lib/src/pan/scroll_physics.dart | 4 +-- 6 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 lib/widgets/common/behaviour/springy_scroll_physics.dart diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index 058206239..189766023 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -84,7 +84,7 @@ class DurationsData { final Duration tvImageFocusAnimation; // viewer animations - final Duration viewerVerticalPageScrollAnimation; + final Duration viewerHorizontalPageScrollAnimation; final Duration viewerOverlayAnimation; final Duration viewerOverlayChangeAnimation; @@ -102,7 +102,7 @@ class DurationsData { this.staggeredAnimationPageTarget = const Duration(milliseconds: 800), this.quickChooserAnimation = const Duration(milliseconds: 100), 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.viewerOverlayChangeAnimation = const Duration(milliseconds: 150), }) : staggeredAnimationDelay = staggeredAnimation ~/ 6; @@ -120,7 +120,7 @@ class DurationsData { staggeredAnimationPageTarget: Duration.zero, quickChooserAnimation: Duration.zero, tvImageFocusAnimation: Duration.zero, - viewerVerticalPageScrollAnimation: Duration.zero, + viewerHorizontalPageScrollAnimation: Duration.zero, viewerOverlayAnimation: Duration.zero, viewerOverlayChangeAnimation: Duration.zero, ); diff --git a/lib/widgets/common/behaviour/springy_scroll_physics.dart b/lib/widgets/common/behaviour/springy_scroll_physics.dart new file mode 100644 index 000000000..1b39c0540 --- /dev/null +++ b/lib/widgets/common/behaviour/springy_scroll_physics.dart @@ -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), + ); + } + +} diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index d6cb80f3f..f03de1f69 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -10,6 +10,7 @@ import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.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/controls/controller.dart'; import 'package:aves/widgets/viewer/controls/intents.dart'; @@ -37,6 +38,13 @@ class ViewerVerticalPageView extends StatefulWidget { final VoidCallback onImagePageRequested; 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({ super.key, required this.collection, @@ -180,7 +188,9 @@ class _ViewerVerticalPageViewState extends State { controller: widget.verticalPager, physics: MagnifierScrollerPhysics( gestureSettings: context.select((mq) => mq.gestureSettings), - parent: const PageScrollPhysics(), + parent: SpringyScrollPhysics( + spring: ViewerVerticalPageView.spring, + ), ), onPageChanged: widget.onVerticalPageChanged, children: pages, diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index f4dd04983..fa4d3cf33 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -9,6 +9,7 @@ import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/trash.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/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -62,7 +63,7 @@ class EntryViewerStack extends StatefulWidget { State createState() => _EntryViewerStackState(); } -class _EntryViewerStackState extends State with EntryViewControllerMixin, FeedbackMixin, SingleTickerProviderStateMixin { +class _EntryViewerStackState extends State with EntryViewControllerMixin, FeedbackMixin, TickerProviderStateMixin { final Floating _floating = Floating(); late int _currentEntryIndex; late ValueNotifier _currentVerticalPage; @@ -72,7 +73,7 @@ class _EntryViewerStackState extends State with EntryViewContr final ValueNotifier _overlayVisible = ValueNotifier(true); final ValueNotifier _viewLocked = ValueNotifier(false); final ValueNotifier _overlayExpandedNotifier = ValueNotifier(false); - late AnimationController _overlayAnimationController; + late AnimationController _verticalPageAnimationController, _overlayAnimationController; late Animation _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity; late Animation _overlayTopOffset; EdgeInsets? _frozenViewInsets, _frozenViewPadding; @@ -125,6 +126,8 @@ class _EntryViewerStackState extends State with EntryViewContr _currentVerticalPage = ValueNotifier(imagePage); _horizontalPager = PageController(initialPage: _currentEntryIndex); _verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChanged); + _verticalPageAnimationController = AnimationController.unbounded(vsync: this); + _verticalPageAnimationController.addListener(() => _verticalPager.jumpTo(_verticalPageAnimationController.value)); _overlayAnimationController = AnimationController( duration: context.read().viewerOverlayAnimation, vsync: this, @@ -171,6 +174,7 @@ class _EntryViewerStackState extends State with EntryViewContr _floating.dispose(); cleanEntryControllers(entryNotifier.value); _videoActionDelegate.dispose(); + _verticalPageAnimationController.dispose(); _overlayAnimationController.dispose(); _overlayVisible.dispose(); _viewLocked.dispose(); @@ -606,14 +610,11 @@ class _EntryViewerStackState extends State with EntryViewContr } Future _goToVerticalPage(int page) async { - final animationDuration = context.read().viewerVerticalPageScrollAnimation; - if (animationDuration > Duration.zero) { - // duration & curve should feel similar to changing page by vertical fling - await _verticalPager.animateToPage( - page, - duration: animationDuration, - curve: Curves.easeOutQuart, - ); + if (settings.accessibilityAnimations.animate) { + final start = _verticalPager.offset; + final end = _verticalPager.position.viewportDimension * page; + final simulation = ScrollSpringSimulation(ViewerVerticalPageView.spring, start, end, 0); + unawaited(_verticalPageAnimationController.animateWith(simulation)); } else { _verticalPager.jumpToPage(page); } @@ -653,13 +654,13 @@ class _EntryViewerStackState extends State with EntryViewContr page = page.clamp(0, _collection.entryCount - 1); } if (_currentEntryIndex != page) { - final animationDuration = animate ? context.read().viewerVerticalPageScrollAnimation : Duration.zero; + final animationDuration = animate ? context.read().viewerHorizontalPageScrollAnimation : 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( page, duration: animationDuration, - curve: Curves.easeOutQuart, + curve: Curves.easeOutCubic, ); } else { _horizontalPager.jumpToPage(page); diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 78b65ce3e..440427f9f 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -124,11 +124,10 @@ class _InfoPageState extends State { } void _goToViewer() { - final animationDuration = context.read().viewerVerticalPageScrollAnimation; ShowImageNotification().dispatch(context); _scrollController.animateTo( 0, - duration: animationDuration, + duration: Durations.pageTransitionAnimation, curve: Curves.easeInOut, ); } diff --git a/plugins/aves_magnifier/lib/src/pan/scroll_physics.dart b/plugins/aves_magnifier/lib/src/pan/scroll_physics.dart index ca4f12137..30b2ce70f 100644 --- a/plugins/aves_magnifier/lib/src/pan/scroll_physics.dart +++ b/plugins/aves_magnifier/lib/src/pan/scroll_physics.dart @@ -17,8 +17,8 @@ class MagnifierScrollerPhysics extends ScrollPhysics { const MagnifierScrollerPhysics({ required this.gestureSettings, this.touchSlopFactor = 1, - ScrollPhysics? parent, - }) : super(parent: parent); + super.parent, + }); @override MagnifierScrollerPhysics applyTo(ScrollPhysics? ancestor) {