From e42886b76726feaebcfae9db23c92cc5cb7bdd88 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 18 Sep 2025 15:59:58 -0500 Subject: [PATCH] fix: display thumbnail while scrubbing paused (#22164) * fix: display thumbnail while scrubbing paused * pr feedback * pr feedback * tune timeout --- mobile/ios/Runner.xcodeproj/project.pbxproj | 12 ++----- .../widgets/timeline/fixed/segment.model.dart | 1 - .../widgets/timeline/scrubber.widget.dart | 35 ++++++++++++++++--- .../widgets/timeline/timeline.state.dart | 2 -- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index e8dc4f00f5..48826a20f1 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -133,6 +133,8 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); path = Sync; sourceTree = ""; }; @@ -519,14 +521,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -555,14 +553,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 50d04f646d..8121bb76d3 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -101,7 +101,6 @@ class _FixedSegmentRow extends ConsumerWidget { if (isScrubbing) { return _buildPlaceholder(context); } - if (timelineService.hasRange(assetIndex, assetCount)) { return _buildAssetRow(context, timelineService.getAssets(assetIndex, assetCount), timelineService); } diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart index 5974f3e0a7..9ea88a88ce 100644 --- a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/utils/debounce.dart'; import 'package:intl/intl.dart' hide TextDirection; /// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged @@ -81,6 +82,8 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi bool _isDragging = false; List<_Segment> _segments = []; int _monthCount = 0; + DateTime? _currentScrubberDate; + Debouncer? _scrubberDebouncer; late AnimationController _thumbAnimationController; Timer? _fadeOutTimer; @@ -133,6 +136,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi _thumbAnimationController.dispose(); _labelAnimationController.dispose(); _fadeOutTimer?.cancel(); + _scrubberDebouncer?.dispose(); super.dispose(); } @@ -176,11 +180,25 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi return false; } - void _onDragStart(DragStartDetails _) { - if (_monthCount >= kMinMonthsToEnableScrubberSnap) { + void _onScrubberDateChanged(DateTime date) { + if (_currentScrubberDate != date) { + // Date changed, immediately set scrubbing to true + _currentScrubberDate = date; ref.read(timelineStateProvider.notifier).setScrubbing(true); - } + // Initialize debouncer if needed + _scrubberDebouncer ??= Debouncer(interval: const Duration(milliseconds: 50)); + + // Debounce setting scrubbing to false + _scrubberDebouncer!.run(() { + if (_currentScrubberDate == date) { + ref.read(timelineStateProvider.notifier).setScrubbing(false); + } + }); + } + } + + void _onDragStart(DragStartDetails _) { setState(() { _isDragging = true; _labelAnimationController.forward(); @@ -206,6 +224,11 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi if (_lastLabel != label) { ref.read(hapticFeedbackProvider.notifier).selectionClick(); _lastLabel = label; + + // Notify timeline state of the new scrubber date position + if (_monthCount >= kMinMonthsToEnableScrubberSnap) { + _onScrubberDateChanged(nearestMonthSegment.date); + } } } @@ -294,12 +317,16 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi } void _onDragEnd(DragEndDetails _) { - ref.read(timelineStateProvider.notifier).setScrubbing(false); _labelAnimationController.reverse(); setState(() { _isDragging = false; }); + // Reset scrubber tracking when drag ends + _currentScrubberDate = null; + _scrubberDebouncer?.dispose(); + _scrubberDebouncer = null; + _resetThumbTimer(); } diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index da1f7fcc9d..b3aec23f7f 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -72,8 +72,6 @@ class TimelineState { } class TimelineStateNotifier extends Notifier { - TimelineStateNotifier(); - void setScrubbing(bool isScrubbing) { state = state.copyWith(isScrubbing: isScrubbing); }