From a582d3a03ed7dccbe5a928de1088e1b6045acf81 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Mon, 22 Sep 2025 01:12:30 +0530 Subject: [PATCH] fix: retain scroll position on scale update (#22237) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../widgets/timeline/timeline.widget.dart | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 98831403f6..c38116c64e 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -107,7 +107,7 @@ class _SliverTimeline extends ConsumerStatefulWidget { } class _SliverTimelineState extends ConsumerState<_SliverTimeline> { - final _scrollController = ScrollController(); + late final ScrollController _scrollController; StreamSubscription? _eventSubscription; // Drag selection state @@ -119,10 +119,12 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { int _perRow = 4; double _scaleFactor = 3.0; double _baseScaleFactor = 3.0; + int? _scaleRestoreAssetIndex; @override void initState() { super.initState(); + _scrollController = ScrollController(onAttach: _restoreScalePosition); _eventSubscription = EventStream.shared.listen(_onEvent); final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); @@ -154,6 +156,28 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { EventStream.shared.emit(MultiSelectToggleEvent(isEnabled)); } + void _restoreScalePosition(_) { + if (_scaleRestoreAssetIndex == null) return; + + final asyncSegments = ref.read(timelineSegmentProvider); + asyncSegments.whenData((segments) { + final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _scaleRestoreAssetIndex!); + if (targetSegment != null) { + final assetIndexInSegment = _scaleRestoreAssetIndex! - targetSegment.firstAssetIndex; + final newColumnCount = ref.read(timelineArgsProvider).columnCount; + final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor(); + final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment; + final targetOffset = targetSegment.indexToLayoutOffset(targetRowIndex); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _scrollController.jumpTo(targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent)); + } + }); + } + }); + _scaleRestoreAssetIndex = null; + } + @override void dispose() { _scrollController.dispose(); @@ -345,9 +369,28 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { final newPerRow = 7 - newScaleFactor.toInt(); if (newPerRow != _perRow) { + final currentOffset = _scrollController.offset.clamp( + 0.0, + _scrollController.position.maxScrollExtent, + ); + final segment = segments.findByOffset(currentOffset) ?? segments.lastOrNull; + int? targetAssetIndex; + if (segment != null) { + final rowIndex = segment.getMinChildIndexForScrollOffset(currentOffset); + if (rowIndex > segment.firstIndex) { + final rowIndexInSegment = rowIndex - (segment.firstIndex + 1); + final assetsPerRow = ref.read(timelineArgsProvider).columnCount; + final assetIndexInSegment = rowIndexInSegment * assetsPerRow; + targetAssetIndex = segment.firstAssetIndex + assetIndexInSegment; + } else { + targetAssetIndex = segment.firstAssetIndex; + } + } + setState(() { _scaleFactor = newScaleFactor; _perRow = newPerRow; + _scaleRestoreAssetIndex = targetAssetIndex; }); ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);