diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 0cfc0c57e3..60c9b92cf9 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -45,3 +45,5 @@ const List<(String, String)> kWidgetNames = [ const double kUploadStatusFailed = -1.0; const double kUploadStatusCanceled = -2.0; + +const int kMinMonthsToEnableScrubberSnap = 12; diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart index 6bac44cb86..10c794b8be 100644 --- a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart @@ -3,14 +3,15 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; 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:intl/intl.dart' hide TextDirection; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:intl/intl.dart' hide TextDirection; /// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged /// for quick navigation of the BoxScrollView. @@ -79,6 +80,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi double _thumbTopOffset = 0.0; bool _isDragging = false; List<_Segment> _segments = []; + int _monthCount = 0; late AnimationController _thumbAnimationController; Timer? _fadeOutTimer; @@ -105,6 +107,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi _thumbAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration); _thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastEaseInToSlowEaseOut); _labelAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration); + _monthCount = getMonthCount(); _labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn); } @@ -121,6 +124,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi if (oldWidget.layoutSegments.lastOrNull?.endOffset != widget.layoutSegments.lastOrNull?.endOffset) { _segments = _buildSegments(layoutSegments: widget.layoutSegments, timelineHeight: _scrubberHeight); + _monthCount = getMonthCount(); } } @@ -140,6 +144,10 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi }); } + int getMonthCount() { + return _segments.map((e) => "${e.date.month}_${e.date.year}").toSet().length; + } + bool _onScrollNotification(ScrollNotification notification) { if (_isDragging) { // If the user is dragging the thumb, we don't want to update the position @@ -169,7 +177,10 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi } void _onDragStart(DragStartDetails _) { - ref.read(timelineStateProvider.notifier).setScrubbing(true); + if (_monthCount >= kMinMonthsToEnableScrubberSnap) { + ref.read(timelineStateProvider.notifier).setScrubbing(true); + } + setState(() { _isDragging = true; _labelAnimationController.forward(); @@ -191,13 +202,22 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi final nearestMonthSegment = _findNearestMonthSegment(dragPosition); if (nearestMonthSegment != null) { - _snapToSegment(nearestMonthSegment); final label = nearestMonthSegment.scrollLabel; if (_lastLabel != label) { ref.read(hapticFeedbackProvider.notifier).selectionClick(); _lastLabel = label; } } + + if (_monthCount < kMinMonthsToEnableScrubberSnap) { + // If there are less than kMinMonthsToEnableScrubberSnap months, we don't need to snap to segments + setState(() { + _thumbTopOffset = dragPosition; + _scrollController.jumpTo((dragPosition / _scrubberHeight) * _scrollController.position.maxScrollExtent); + }); + } else if (nearestMonthSegment != null) { + _snapToSegment(nearestMonthSegment); + } } /// Calculate the drag position relative to the scrubber area