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 84edc4df65..98a158ba15 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -48,6 +48,9 @@ class _AssetPageState extends ConsumerState { bool _showingDetails = false; bool _isZoomed = false; + // Frozen during dismiss drag + settle to prevent widget tree swap mid-animation. + bool _frozenMotionPlaying = false; + bool _dismissSettling = false; final _scrollController = SnapScrollController(); double _snapOffset = 0.0; @@ -152,6 +155,9 @@ class _AssetPageState extends ConsumerState { > 0 => _DragIntent.dismiss, _ => _DragIntent.none, }; + if (_dragIntent == _DragIntent.dismiss) { + _frozenMotionPlaying = ref.read(isPlayingMotionVideoProvider); + } } switch (_dragIntent) { @@ -193,12 +199,18 @@ class _AssetPageState extends ConsumerState { context.maybePop(); return; } - _viewController?.animateMultiple( - position: _initialPhotoViewState.position, - scale: _viewController?.initialScale ?? _initialPhotoViewState.scale, - rotation: _initialPhotoViewState.rotation, - ); _viewer.setOpacity(1.0); + _dismissSettling = true; + _viewController + ?.animateMultiple( + position: _initialPhotoViewState.position, + scale: _viewController?.initialScale ?? _initialPhotoViewState.scale, + rotation: _initialPhotoViewState.rotation, + ) + .whenComplete(() { + if (!mounted) return; + setState(() => _dismissSettling = false); + }); } } @@ -392,7 +404,10 @@ class _AssetPageState extends ConsumerState { final currentHeroTag = ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag)); _showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex)); - final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); + final liveMotionPlaying = ref.watch(isPlayingMotionVideoProvider); + final isPlayingMotionVideo = (_dragIntent == _DragIntent.dismiss || _dismissSettling) + ? _frozenMotionPlaying + : liveMotionPlaying; final asset = _asset; if (asset == null) { diff --git a/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart b/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart index b9475a9ee2..c86591fc83 100644 --- a/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart +++ b/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart @@ -38,12 +38,13 @@ abstract class PhotoViewControllerBase { /// Closes streams and removes eventual listeners. void dispose(); - void positionAnimationBuilder(void Function(Offset)? value); - void scaleAnimationBuilder(void Function(double)? value); - void rotationAnimationBuilder(void Function(double)? value); + void positionAnimationBuilder(Future Function(Offset)? value); + void scaleAnimationBuilder(Future Function(double)? value); + void rotationAnimationBuilder(Future Function(double)? value); - /// Animates multiple fields of the state - void animateMultiple({Offset? position, double? scale, double? rotation}); + /// Animates multiple fields of the state. The returned future completes + /// when all underlying animations have settled. + Future animateMultiple({Offset? position, double? scale, double? rotation}); /// Add a listener that will ignore updates made internally /// @@ -148,9 +149,9 @@ class PhotoViewController implements PhotoViewControllerBase Function(Offset)? _animatePosition; + late Future Function(double)? _animateScale; + late Future Function(double)? _animateRotation; @override Stream get outputStateStream => _outputCtrl.stream; @@ -159,17 +160,17 @@ class PhotoViewController implements PhotoViewControllerBase Function(Offset)? value) { _animatePosition = value; } @override - void scaleAnimationBuilder(void Function(double)? value) { + void scaleAnimationBuilder(Future Function(double)? value) { _animateScale = value; } @override - void rotationAnimationBuilder(void Function(double)? value) { + void rotationAnimationBuilder(Future Function(double)? value) { _animateRotation = value; } @@ -193,18 +194,18 @@ class PhotoViewController implements PhotoViewControllerBase animateMultiple({Offset? position, double? scale, double? rotation}) { + final futures = >[]; if (position != null && _animatePosition != null) { - _animatePosition!(position); + futures.add(_animatePosition!(position)); } - if (scale != null && _animateScale != null) { - _animateScale!(scale); + futures.add(_animateScale!(scale)); } - if (rotation != null && _animateRotation != null) { - _animateRotation!(rotation); + futures.add(_animateRotation!(rotation)); } + return Future.wait(futures); } @override diff --git a/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart b/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart index 265feb756e..eecc7667b8 100644 --- a/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart +++ b/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart @@ -235,34 +235,31 @@ class PhotoViewCoreState extends State nextScaleState(); } - void animateScale(double from, double to) { + Future animateScale(double from, double to) { if (!mounted) { - return; + return Future.value(); } _scaleAnimation = Tween(begin: from, end: to).animate(_scaleAnimationController); - _scaleAnimationController - ..value = 0.0 - ..fling(velocity: 0.4); + _scaleAnimationController.value = 0.0; + return _scaleAnimationController.fling(velocity: 0.4); } - void animatePosition(Offset from, Offset to) { + Future animatePosition(Offset from, Offset to) { if (!mounted) { - return; + return Future.value(); } _positionAnimation = Tween(begin: from, end: to).animate(_positionAnimationController); - _positionAnimationController - ..value = 0.0 - ..fling(velocity: 0.4); + _positionAnimationController.value = 0.0; + return _positionAnimationController.fling(velocity: 0.4); } - void animateRotation(double from, double to) { + Future animateRotation(double from, double to) { if (!mounted) { - return; + return Future.value(); } _rotationAnimation = Tween(begin: from, end: to).animate(_rotationAnimationController); - _rotationAnimationController - ..value = 0.0 - ..fling(velocity: 0.4); + _rotationAnimationController.value = 0.0; + return _rotationAnimationController.fling(velocity: 0.4); } void onAnimationStatus(AnimationStatus status) { @@ -278,18 +275,19 @@ class PhotoViewCoreState extends State } } - void _animateControllerPosition(Offset position) { - animatePosition(controller.position, position); + Future _animateControllerPosition(Offset position) { + return animatePosition(controller.position, position); } - void _animateControllerScale(double scale) { + Future _animateControllerScale(double scale) { if (controller.scale != null) { - animateScale(controller.scale!, scale); + return animateScale(controller.scale!, scale); } + return Future.value(); } - void _animateControllerRotation(double rotation) { - animateRotation(controller.rotation, rotation); + Future _animateControllerRotation(double rotation) { + return animateRotation(controller.rotation, rotation); } @override