fix: scrolling state update after unmounted (#29149)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2026-06-16 13:14:09 -05:00
committed by GitHub
co-authored by shenlong-tanwen
parent a9d64b30ad
commit 83b4dc1733
2 changed files with 23 additions and 18 deletions
@@ -110,9 +110,6 @@ class _FixedSegmentRow extends ConsumerWidget {
final timelineService = ref.read(timelineServiceProvider);
final isDynamicLayout = columnCount <= (context.isMobile ? 2 : 3);
if (isScrubbing) {
return _buildPlaceholder(context);
}
if (timelineService.hasRange(assetIndex, assetCount)) {
return _buildAssetRow(
context,
@@ -122,6 +119,10 @@ class _FixedSegmentRow extends ConsumerWidget {
);
}
if (isScrubbing) {
return _buildPlaceholder(context);
}
return FutureBuilder<List<BaseAsset>>(
future: timelineService.loadAssets(assetIndex, assetCount),
builder: (context, snapshot) {
@@ -21,8 +21,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.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_drag_region.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
@@ -181,10 +181,13 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
void _onEvent(Event event) {
switch (event) {
case ScrollToTopEvent():
ref.read(timelineStateProvider.notifier).setScrubbing(true);
_scrollController
.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut)
.whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false));
{
final timelineState = ref.read(timelineStateProvider.notifier);
timelineState.setScrubbing(true);
_scrollController
.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut)
.whenComplete(() => timelineState.setScrubbing(false));
}
case ScrollToDateEvent scrollToDateEvent:
_scrollToDate(scrollToDateEvent.date);
@@ -249,6 +252,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
}
void _scrollToDate(DateTime date) {
final timelineState = ref.read(timelineStateProvider.notifier);
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
// Find the segment that contains assets from the target date
@@ -275,16 +279,16 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
if (fallbackSegment != null) {
// Scroll to the segment with a small offset to show the header
final targetOffset = fallbackSegment.startOffset - 50;
ref.read(timelineStateProvider.notifier).setScrubbing(true);
timelineState.setScrubbing(true);
_scrollController
.animateTo(
targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent),
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
)
.whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false));
.whenComplete(() => timelineState.setScrubbing(false));
} else {
ref.read(timelineStateProvider.notifier).setScrubbing(false);
timelineState.setScrubbing(false);
}
});
}
@@ -301,19 +305,19 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
void _stopDrag() {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Update the physics post frame to prevent sudden change in physics on iOS.
setState(() {
_scrollPhysics = null;
});
if (mounted) {
setState(() {
_scrollPhysics = null;
});
}
});
setState(() {
_dragging = false;
_draggedAssets.clear();
});
// Reset the scrolling state after a small delay to allow bottom sheet to expand again
final timelineState = ref.read(timelineStateProvider.notifier);
Future.delayed(const Duration(milliseconds: 300), () {
if (mounted) {
ref.read(timelineStateProvider.notifier).setScrolling(false);
}
timelineState.setScrolling(false);
});
}