diff --git a/mobile/lib/extensions/scroll_extensions.dart b/mobile/lib/extensions/scroll_extensions.dart index 5917e127bc..5b8f9e2a13 100644 --- a/mobile/lib/extensions/scroll_extensions.dart +++ b/mobile/lib/extensions/scroll_extensions.dart @@ -33,12 +33,27 @@ class FastClampingScrollPhysics extends ClampingScrollPhysics { ); } +class SnapScrollController extends ScrollController { + SnapScrollPosition get snapPosition => position as SnapScrollPosition; + + @override + ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) => + SnapScrollPosition(physics: physics, context: context, oldPosition: oldPosition); +} + +class SnapScrollPosition extends ScrollPositionWithSingleContext { + double snapOffset; + + SnapScrollPosition({required super.physics, required super.context, super.oldPosition, this.snapOffset = 0.0}); + + @override + bool get shouldIgnorePointer => false; +} + class SnapScrollPhysics extends ScrollPhysics { static const _minFlingVelocity = 700.0; static const minSnapDistance = 30.0; - static final _spring = SpringDescription.withDampingRatio(mass: .5, stiffness: 300); - const SnapScrollPhysics({super.parent}); @override @@ -66,91 +81,21 @@ class SnapScrollPhysics extends ScrollPhysics { } } - return ScrollSpringSimulation( - _spring, - position.pixels, - target(position, velocity, snapOffset), - velocity, - tolerance: toleranceFor(position), - ); + return ScrollSpringSimulation(spring, position.pixels, target(position, velocity, snapOffset), velocity); } + @override + SpringDescription get spring => SpringDescription.withDampingRatio(mass: .5, stiffness: 300); + + @override + bool get allowImplicitScrolling => false; + + @override + bool get allowUserScrolling => false; + static double target(ScrollMetrics position, double velocity, double snapOffset) { if (velocity > _minFlingVelocity) return snapOffset; if (velocity < -_minFlingVelocity) return position.pixels < snapOffset ? 0.0 : snapOffset; return position.pixels < minSnapDistance ? 0.0 : snapOffset; } } - -class SnapScrollPosition extends ScrollPositionWithSingleContext { - double snapOffset; - - SnapScrollPosition({this.snapOffset = 0.0, required super.physics, required super.context, super.oldPosition}); -} - -class ProxyScrollController extends ScrollController { - final ScrollController scrollController; - - ProxyScrollController({required this.scrollController}); - - SnapScrollPosition get snapPosition => position as SnapScrollPosition; - - @override - ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) { - return ProxyScrollPosition( - scrollController: scrollController, - physics: physics, - context: context, - oldPosition: oldPosition, - ); - } - - @override - void dispose() { - scrollController.dispose(); - super.dispose(); - } -} - -class ProxyScrollPosition extends SnapScrollPosition { - final ScrollController scrollController; - - ProxyScrollPosition({ - required this.scrollController, - required super.physics, - required super.context, - super.oldPosition, - }); - - @override - double setPixels(double newPixels) { - final overscroll = super.setPixels(newPixels); - if (scrollController.hasClients && scrollController.position.pixels != pixels) { - scrollController.position.forcePixels(pixels); - } - return overscroll; - } - - @override - void forcePixels(double value) { - super.forcePixels(value); - if (scrollController.hasClients && scrollController.position.pixels != pixels) { - scrollController.position.forcePixels(pixels); - } - } - - @override - double get maxScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions - ? scrollController.position.maxScrollExtent - : super.maxScrollExtent; - - @override - double get minScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions - ? scrollController.position.minScrollExtent - : super.minScrollExtent; - - @override - double get viewportDimension => scrollController.hasClients && scrollController.position.hasViewportDimension - ? scrollController.position.viewportDimension - : super.viewportDimension; -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart index 5da8227ef0..ea7ff51fa6 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -50,8 +50,7 @@ class _AssetPageState extends ConsumerState { bool _showingDetails = false; bool _isZoomed = false; - final _scrollController = ScrollController(); - late final _proxyScrollController = ProxyScrollController(scrollController: _scrollController); + final _scrollController = SnapScrollController(); double _snapOffset = 0.0; DragStartDetails? _dragStart; @@ -63,17 +62,17 @@ class _AssetPageState extends ConsumerState { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || !_proxyScrollController.hasClients) return; - _proxyScrollController.snapPosition.snapOffset = _snapOffset; + if (!mounted || !_scrollController.hasClients) return; + _scrollController.snapPosition.snapOffset = _snapOffset; if (_showingDetails && _snapOffset > 0) { - _proxyScrollController.jumpTo(_snapOffset); + _scrollController.jumpTo(_snapOffset); } }); } @override void dispose() { - _proxyScrollController.dispose(); + _scrollController.dispose(); _scaleBoundarySub?.cancel(); _eventSubscription?.cancel(); super.dispose(); @@ -88,21 +87,20 @@ class _AssetPageState extends ConsumerState { } void _showDetails() { - if (!_proxyScrollController.hasClients || _snapOffset <= 0) return; + if (!_scrollController.hasClients || _snapOffset <= 0) return; _viewer.setShowingDetails(true); - _proxyScrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic); + _scrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic); } - bool _willClose(double scrollVelocity) { - if (!_proxyScrollController.hasClients || _snapOffset <= 0) return false; - - final position = _proxyScrollController.position; - return _proxyScrollController.position.pixels < _snapOffset && - SnapScrollPhysics.target(position, scrollVelocity, _snapOffset) < SnapScrollPhysics.minSnapDistance; - } + bool _willClose(double scrollVelocity) => + _scrollController.hasClients && + _snapOffset > 0 && + _scrollController.position.pixels < _snapOffset && + SnapScrollPhysics.target(_scrollController.position, scrollVelocity, _snapOffset) < + SnapScrollPhysics.minSnapDistance; void _syncShowingDetails() { - final offset = _proxyScrollController.offset; + final offset = _scrollController.offset; if (offset > SnapScrollPhysics.minSnapDistance) { _viewer.setShowingDetails(true); } else if (offset < SnapScrollPhysics.minSnapDistance - kTouchSlop) { @@ -124,8 +122,8 @@ class _AssetPageState extends ConsumerState { } void _startProxyDrag() { - if (_proxyScrollController.hasClients && _dragStart != null) { - _drag = _proxyScrollController.position.drag(_dragStart!, () => _drag = null); + if (_scrollController.hasClients && _dragStart != null) { + _drag = _scrollController.position.drag(_dragStart!, () => _drag = null); } } @@ -390,22 +388,15 @@ class _AssetPageState extends ConsumerState { _snapOffset = detailsOffset - snapTarget; - if (_proxyScrollController.hasClients) { - _proxyScrollController.snapPosition.snapOffset = _snapOffset; + if (_scrollController.hasClients) { + _scrollController.snapPosition.snapOffset = _snapOffset; } return Stack( children: [ - Offstage( - child: SingleChildScrollView( - controller: _proxyScrollController, - physics: const SnapScrollPhysics(), - child: const SizedBox.shrink(), - ), - ), SingleChildScrollView( controller: _scrollController, - physics: const NeverScrollableScrollPhysics(), + physics: const SnapScrollPhysics(), child: Stack( children: [ SizedBox(