diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index da0497539b..9f7c695c8b 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -29,7 +29,38 @@ import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart'; -class Timeline extends ConsumerWidget { +class _TimelineRestorationState extends ChangeNotifier { + int? _restoreAssetIndex; + bool _shouldRestoreAssetPosition = false; + + int? get restoreAssetIndex => _restoreAssetIndex; + bool get shouldRestoreAssetPosition => _shouldRestoreAssetPosition; + + void setRestoreAssetIndex(int? index) { + _restoreAssetIndex = index; + notifyListeners(); + } + + void setShouldRestoreAssetPosition(bool should) { + _shouldRestoreAssetPosition = should; + notifyListeners(); + } + + void clearRestoreAssetIndex() { + _restoreAssetIndex = null; + notifyListeners(); + } +} + +class _TimelineRestorationProvider extends InheritedNotifier<_TimelineRestorationState> { + const _TimelineRestorationProvider({required super.notifier, required super.child}); + + static _TimelineRestorationState of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType<_TimelineRestorationProvider>()!.notifier!; + } +} + +class Timeline extends StatefulWidget { const Timeline({ super.key, this.topSliverWidget, @@ -58,36 +89,66 @@ class Timeline extends ConsumerWidget { final bool readOnly; @override - Widget build(BuildContext context, WidgetRef ref) { + State createState() => _TimelineState(); +} + +class _TimelineState extends State { + double? _lastWidth; + late final _TimelineRestorationState _restorationState; + + @override + void initState() { + super.initState(); + _restorationState = _TimelineRestorationState(); + } + + @override + void dispose() { + _restorationState.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, floatingActionButton: const DownloadStatusFloatingButton(), body: LayoutBuilder( - builder: (_, constraints) => ProviderScope( - overrides: [ - timelineArgsProvider.overrideWithValue( - TimelineArgs( - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight, - columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), - showStorageIndicator: showStorageIndicator, - withStack: withStack, - groupBy: groupBy, + builder: (_, constraints) { + if (_lastWidth != null && _lastWidth != constraints.maxWidth) { + _restorationState.setShouldRestoreAssetPosition(true); + } + _lastWidth = constraints.maxWidth; + return _TimelineRestorationProvider( + notifier: _restorationState, + child: ProviderScope( + key: ValueKey(_lastWidth), + overrides: [ + timelineArgsProvider.overrideWith( + (ref) => TimelineArgs( + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), + showStorageIndicator: widget.showStorageIndicator, + withStack: widget.withStack, + groupBy: widget.groupBy, + ), + ), + if (widget.readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), + ], + child: _SliverTimeline( + key: const ValueKey('_sliver_timeline'), + topSliverWidget: widget.topSliverWidget, + topSliverWidgetHeight: widget.topSliverWidgetHeight, + appBar: widget.appBar, + bottomSheet: widget.bottomSheet, + withScrubber: widget.withScrubber, + snapToMonth: widget.snapToMonth, + initialScrollOffset: widget.initialScrollOffset, ), ), - if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), - ], - child: _SliverTimeline( - key: const ValueKey('_sliver_timeline'), - topSliverWidget: topSliverWidget, - topSliverWidgetHeight: topSliverWidgetHeight, - appBar: appBar, - bottomSheet: bottomSheet, - withScrubber: withScrubber, - snapToMonth: snapToMonth, - initialScrollOffset: initialScrollOffset, - ), - ), + ); + }, ), ); } @@ -141,7 +202,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { int _perRow = 4; double _scaleFactor = 3.0; double _baseScaleFactor = 3.0; - int? _restoreAssetIndex; @override void initState() { @@ -182,13 +242,16 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { } void _restoreAssetPosition(_) { - if (_restoreAssetIndex == null) return; + final restorationState = _TimelineRestorationProvider.of(context); + if (!restorationState.shouldRestoreAssetPosition || restorationState.restoreAssetIndex == null) return; final asyncSegments = ref.read(timelineSegmentProvider); asyncSegments.whenData((segments) { - final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _restoreAssetIndex!); + final targetSegment = segments.lastWhereOrNull( + (segment) => segment.firstAssetIndex <= restorationState.restoreAssetIndex!, + ); if (targetSegment != null) { - final assetIndexInSegment = _restoreAssetIndex! - targetSegment.firstAssetIndex; + final assetIndexInSegment = restorationState.restoreAssetIndex! - targetSegment.firstAssetIndex; final newColumnCount = ref.read(timelineArgsProvider).columnCount; final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor(); final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment; @@ -200,7 +263,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { }); } }); - _restoreAssetIndex = null; + restorationState.clearRestoreAssetIndex(); } int? _getCurrentAssetIndex(List segments) { @@ -411,7 +474,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { onNotification: (notification) { final currentIndex = _getCurrentAssetIndex(segments); if (currentIndex != null && mounted) { - _restoreAssetIndex = currentIndex; + _TimelineRestorationProvider.of(context).setRestoreAssetIndex(currentIndex); } return false; }, @@ -430,12 +493,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { final targetAssetIndex = _getCurrentAssetIndex(segments); if (newPerRow != _perRow) { + final restorationState = _TimelineRestorationProvider.of(context); setState(() { _scaleFactor = newScaleFactor; _perRow = newPerRow; - _restoreAssetIndex = targetAssetIndex; }); + restorationState.setRestoreAssetIndex(targetAssetIndex); + restorationState.setShouldRestoreAssetPosition(true); ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow); } };