fix: incorrect asset viewer scale on image frame update (#25430)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2026-01-23 03:16:30 +05:30 committed by GitHub
parent dd72c32c60
commit bccad2940e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 16 deletions

View file

@ -118,7 +118,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
bool dragInProgress = false; bool dragInProgress = false;
bool shouldPopOnDrag = false; bool shouldPopOnDrag = false;
bool assetReloadRequested = false; bool assetReloadRequested = false;
double? initialScale;
double previousExtent = _kBottomSheetMinimumExtent; double previousExtent = _kBottomSheetMinimumExtent;
Offset dragDownPosition = Offset.zero; Offset dragDownPosition = Offset.zero;
int totalAssets = 0; int totalAssets = 0;
@ -264,7 +263,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
(context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent); (context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent);
controller.position = Offset(0, -verticalOffset); controller.position = Offset(0, -verticalOffset);
// Apply the zoom effect when the bottom sheet is showing // Apply the zoom effect when the bottom sheet is showing
initialScale = controller.scale;
controller.scale = (controller.scale ?? 1.0) + 0.01; controller.scale = (controller.scale ?? 1.0) + 0.01;
} }
} }
@ -316,7 +314,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
hasDraggedDown = null; hasDraggedDown = null;
viewController?.animateMultiple( viewController?.animateMultiple(
position: initialPhotoViewState.position, position: initialPhotoViewState.position,
scale: initialPhotoViewState.scale, scale: viewController?.initialScale ?? initialPhotoViewState.scale,
rotation: initialPhotoViewState.rotation, rotation: initialPhotoViewState.rotation,
); );
ref.read(assetViewerProvider.notifier).setOpacity(255); ref.read(assetViewerProvider.notifier).setOpacity(255);
@ -366,8 +364,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
final maxScaleDistance = ctx.height * 0.5; final maxScaleDistance = ctx.height * 0.5;
final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio); final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio);
double? updatedScale; double? updatedScale;
if (initialPhotoViewState.scale != null) { double? initialScale = viewController?.initialScale ?? initialPhotoViewState.scale;
updatedScale = initialPhotoViewState.scale! * (1.0 - scaleReduction); if (initialScale != null) {
updatedScale = initialScale * (1.0 - scaleReduction);
} }
final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round(); final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round();
@ -481,8 +480,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) { void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) {
ref.read(assetViewerProvider.notifier).setBottomSheet(true); ref.read(assetViewerProvider.notifier).setBottomSheet(true);
initialScale = viewController?.scale;
// viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01);
previousExtent = _kBottomSheetMinimumExtent; previousExtent = _kBottomSheetMinimumExtent;
sheetCloseController = showBottomSheet( sheetCloseController = showBottomSheet(
context: ctx, context: ctx,
@ -504,7 +501,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
void _handleSheetClose() { void _handleSheetClose() {
viewController?.animateMultiple(position: Offset.zero); viewController?.animateMultiple(position: Offset.zero);
viewController?.updateMultiple(scale: initialScale); viewController?.updateMultiple(scale: viewController?.initialScale);
ref.read(assetViewerProvider.notifier).setBottomSheet(false); ref.read(assetViewerProvider.notifier).setBottomSheet(false);
sheetCloseController = null; sheetCloseController = null;
shouldPopOnDrag = false; shouldPopOnDrag = false;

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/widgets.dart'; 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/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. /// The interface in which controllers will be implemented.
/// ///
@ -62,6 +63,9 @@ abstract class PhotoViewControllerBase<T extends PhotoViewControllerValue> {
/// The scale factor to transform the child (image or a customChild). /// The scale factor to transform the child (image or a customChild).
late double? scale; late double? scale;
double? get initialScale;
ScaleBoundaries? scaleBoundaries;
/// Nevermind this method :D, look away /// Nevermind this method :D, look away
void setScaleInvisibly(double? scale); void setScaleInvisibly(double? scale);
@ -141,6 +145,9 @@ class PhotoViewController implements PhotoViewControllerBase<PhotoViewController
late StreamController<PhotoViewControllerValue> _outputCtrl; late StreamController<PhotoViewControllerValue> _outputCtrl;
@override
ScaleBoundaries? scaleBoundaries;
late void Function(Offset)? _animatePosition; late void Function(Offset)? _animatePosition;
late void Function(double)? _animateScale; late void Function(double)? _animateScale;
late void Function(double)? _animateRotation; late void Function(double)? _animateRotation;
@ -311,4 +318,7 @@ class PhotoViewController implements PhotoViewControllerBase<PhotoViewController
} }
_valueNotifier.value = newValue; _valueNotifier.value = newValue;
} }
@override
double? get initialScale => scaleBoundaries?.initialScale ?? initial.scale;
} }

View file

@ -108,6 +108,17 @@ class _ImageWrapperState extends State<ImageWrapper> {
} }
} }
// 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 // retrieve image from the provider
void _resolveImage() { void _resolveImage() {
final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration()); final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration());
@ -133,6 +144,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
_lastStack = null; _lastStack = null;
_didLoadSynchronously = synchronousCall; _didLoadSynchronously = synchronousCall;
widget.controller.scaleBoundaries = scaleBoundaries;
} }
synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB); synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB);
@ -204,14 +216,6 @@ class _ImageWrapperState extends State<ImageWrapper> {
); );
} }
final scaleBoundaries = ScaleBoundaries(
widget.minScale ?? 0.0,
widget.maxScale ?? double.infinity,
widget.initialScale ?? PhotoViewComputedScale.contained,
widget.outerSize,
_imageSize!,
);
return PhotoViewCore( return PhotoViewCore(
imageProvider: widget.imageProvider, imageProvider: widget.imageProvider,
backgroundDecoration: widget.backgroundDecoration, backgroundDecoration: widget.backgroundDecoration,