From a797cd6cf98a3c0972bceb4cce96f3eb48e92749 Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Thu, 22 Jan 2026 00:53:42 +0530 Subject: [PATCH] fix: incorrect asset viewer scale on image frame update --- .../asset_viewer/asset_viewer.page.dart | 13 +++++------- .../src/controller/photo_view_controller.dart | 10 ++++++++++ .../photo_view/src/photo_view_wrappers.dart | 20 +++++++++++-------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 38b9c54a3e..e89a41481f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -118,7 +118,6 @@ class _AssetViewerState extends ConsumerState { bool dragInProgress = false; bool shouldPopOnDrag = false; bool assetReloadRequested = false; - double? initialScale; double previousExtent = _kBottomSheetMinimumExtent; Offset dragDownPosition = Offset.zero; int totalAssets = 0; @@ -264,7 +263,6 @@ class _AssetViewerState extends ConsumerState { (context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent); controller.position = Offset(0, -verticalOffset); // Apply the zoom effect when the bottom sheet is showing - initialScale = controller.scale; controller.scale = (controller.scale ?? 1.0) + 0.01; } } @@ -316,7 +314,7 @@ class _AssetViewerState extends ConsumerState { hasDraggedDown = null; viewController?.animateMultiple( position: initialPhotoViewState.position, - scale: initialPhotoViewState.scale, + scale: viewController?.initialScale ?? initialPhotoViewState.scale, rotation: initialPhotoViewState.rotation, ); ref.read(assetViewerProvider.notifier).setOpacity(255); @@ -366,8 +364,9 @@ class _AssetViewerState extends ConsumerState { final maxScaleDistance = ctx.height * 0.5; final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio); double? updatedScale; - if (initialPhotoViewState.scale != null) { - updatedScale = initialPhotoViewState.scale! * (1.0 - scaleReduction); + double? initialScale = viewController?.initialScale ?? initialPhotoViewState.scale; + if (initialScale != null) { + updatedScale = initialScale * (1.0 - scaleReduction); } final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round(); @@ -481,8 +480,6 @@ class _AssetViewerState extends ConsumerState { void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) { ref.read(assetViewerProvider.notifier).setBottomSheet(true); - initialScale = viewController?.scale; - // viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01); previousExtent = _kBottomSheetMinimumExtent; sheetCloseController = showBottomSheet( context: ctx, @@ -504,7 +501,7 @@ class _AssetViewerState extends ConsumerState { void _handleSheetClose() { viewController?.animateMultiple(position: Offset.zero); - viewController?.updateMultiple(scale: initialScale); + viewController?.updateMultiple(scale: viewController?.initialScale); ref.read(assetViewerProvider.notifier).setBottomSheet(false); sheetCloseController = null; shouldPopOnDrag = false; 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 2c8b406385..b9475a9ee2 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 @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/widgets/photo_view/src/utils/ignorable_change_notifier.dart'; +import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_utils.dart'; /// The interface in which controllers will be implemented. /// @@ -62,6 +63,9 @@ abstract class PhotoViewControllerBase { /// The scale factor to transform the child (image or a customChild). late double? scale; + double? get initialScale; + ScaleBoundaries? scaleBoundaries; + /// Nevermind this method :D, look away void setScaleInvisibly(double? scale); @@ -141,6 +145,9 @@ class PhotoViewController implements PhotoViewControllerBase _outputCtrl; + @override + ScaleBoundaries? scaleBoundaries; + late void Function(Offset)? _animatePosition; late void Function(double)? _animateScale; late void Function(double)? _animateRotation; @@ -311,4 +318,7 @@ class PhotoViewController implements PhotoViewControllerBase scaleBoundaries?.initialScale ?? initial.scale; } diff --git a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart index a2ad04e6b5..cd70745703 100644 --- a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart +++ b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart @@ -108,6 +108,17 @@ class _ImageWrapperState extends State { } } + // Should be called only when _imageSize is not null + ScaleBoundaries get scaleBoundaries { + return ScaleBoundaries( + widget.minScale ?? 0.0, + widget.maxScale ?? double.infinity, + widget.initialScale ?? PhotoViewComputedScale.contained, + widget.outerSize, + _imageSize!, + ); + } + // retrieve image from the provider void _resolveImage() { final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration()); @@ -133,6 +144,7 @@ class _ImageWrapperState extends State { _lastStack = null; _didLoadSynchronously = synchronousCall; + widget.controller.scaleBoundaries = scaleBoundaries; } synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB); @@ -204,14 +216,6 @@ class _ImageWrapperState extends State { ); } - final scaleBoundaries = ScaleBoundaries( - widget.minScale ?? 0.0, - widget.maxScale ?? double.infinity, - widget.initialScale ?? PhotoViewComputedScale.contained, - widget.outerSize, - _imageSize!, - ); - return PhotoViewCore( imageProvider: widget.imageProvider, backgroundDecoration: widget.backgroundDecoration,