mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat: disable snapping when a timeline has less than 12 months (#21649)
* feat: disable snapping when a timeline has less than 12 months * fix: disable placeholders when not snapping also moved month constant to constants.dart --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
d38468439b
commit
27d2f3efe2
2 changed files with 25 additions and 3 deletions
|
|
@ -45,3 +45,5 @@ const List<(String, String)> kWidgetNames = [
|
||||||
|
|
||||||
const double kUploadStatusFailed = -1.0;
|
const double kUploadStatusFailed = -1.0;
|
||||||
const double kUploadStatusCanceled = -2.0;
|
const double kUploadStatusCanceled = -2.0;
|
||||||
|
|
||||||
|
const int kMinMonthsToEnableScrubberSnap = 12;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@ import 'dart:async';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_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/constants.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.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/presentation/widgets/timeline/timeline.state.dart';
|
||||||
import 'package:intl/intl.dart' hide TextDirection;
|
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
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
|
/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
|
||||||
/// for quick navigation of the BoxScrollView.
|
/// for quick navigation of the BoxScrollView.
|
||||||
|
|
@ -79,6 +80,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
double _thumbTopOffset = 0.0;
|
double _thumbTopOffset = 0.0;
|
||||||
bool _isDragging = false;
|
bool _isDragging = false;
|
||||||
List<_Segment> _segments = [];
|
List<_Segment> _segments = [];
|
||||||
|
int _monthCount = 0;
|
||||||
|
|
||||||
late AnimationController _thumbAnimationController;
|
late AnimationController _thumbAnimationController;
|
||||||
Timer? _fadeOutTimer;
|
Timer? _fadeOutTimer;
|
||||||
|
|
@ -105,6 +107,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
_thumbAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
_thumbAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
||||||
_thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastEaseInToSlowEaseOut);
|
_thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastEaseInToSlowEaseOut);
|
||||||
_labelAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
_labelAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
||||||
|
_monthCount = getMonthCount();
|
||||||
|
|
||||||
_labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn);
|
_labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn);
|
||||||
}
|
}
|
||||||
|
|
@ -121,6 +124,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
|
|
||||||
if (oldWidget.layoutSegments.lastOrNull?.endOffset != widget.layoutSegments.lastOrNull?.endOffset) {
|
if (oldWidget.layoutSegments.lastOrNull?.endOffset != widget.layoutSegments.lastOrNull?.endOffset) {
|
||||||
_segments = _buildSegments(layoutSegments: widget.layoutSegments, timelineHeight: _scrubberHeight);
|
_segments = _buildSegments(layoutSegments: widget.layoutSegments, timelineHeight: _scrubberHeight);
|
||||||
|
_monthCount = getMonthCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +144,10 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getMonthCount() {
|
||||||
|
return _segments.map((e) => "${e.date.month}_${e.date.year}").toSet().length;
|
||||||
|
}
|
||||||
|
|
||||||
bool _onScrollNotification(ScrollNotification notification) {
|
bool _onScrollNotification(ScrollNotification notification) {
|
||||||
if (_isDragging) {
|
if (_isDragging) {
|
||||||
// If the user is dragging the thumb, we don't want to update the position
|
// If the user is dragging the thumb, we don't want to update the position
|
||||||
|
|
@ -169,7 +177,10 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDragStart(DragStartDetails _) {
|
void _onDragStart(DragStartDetails _) {
|
||||||
ref.read(timelineStateProvider.notifier).setScrubbing(true);
|
if (_monthCount >= kMinMonthsToEnableScrubberSnap) {
|
||||||
|
ref.read(timelineStateProvider.notifier).setScrubbing(true);
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDragging = true;
|
_isDragging = true;
|
||||||
_labelAnimationController.forward();
|
_labelAnimationController.forward();
|
||||||
|
|
@ -191,13 +202,22 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||||
final nearestMonthSegment = _findNearestMonthSegment(dragPosition);
|
final nearestMonthSegment = _findNearestMonthSegment(dragPosition);
|
||||||
|
|
||||||
if (nearestMonthSegment != null) {
|
if (nearestMonthSegment != null) {
|
||||||
_snapToSegment(nearestMonthSegment);
|
|
||||||
final label = nearestMonthSegment.scrollLabel;
|
final label = nearestMonthSegment.scrollLabel;
|
||||||
if (_lastLabel != label) {
|
if (_lastLabel != label) {
|
||||||
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
||||||
_lastLabel = label;
|
_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
|
/// Calculate the drag position relative to the scrubber area
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue