mirror of
https://github.com/immich-app/immich.git
synced 2026-09-29 13:13:19 +02:00
* fix(mobile): use timeline scroll velocity to add placeholders * Switch to using Flutter recommendDeferredLoading * Shared debounce logic
93 lines
3.7 KiB
Dart
93 lines
3.7 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
|
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
|
|
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
|
|
|
part 'timeline.state.freezed.dart';
|
|
|
|
@freezed
|
|
abstract class TimelineArgs with _$TimelineArgs {
|
|
const factory TimelineArgs({
|
|
required double maxWidth,
|
|
required double maxHeight,
|
|
@Default(kTimelineSpacing) double spacing,
|
|
@Default(kTimelineColumnCount) int columnCount,
|
|
@Default(false) bool showStorageIndicator,
|
|
@Default(false) bool withStack,
|
|
GroupAssetsBy? groupBy,
|
|
}) = _TimelineArgs;
|
|
}
|
|
|
|
class TimelineState {
|
|
final bool isScrolling;
|
|
|
|
/// Indicates whether the timeline is scrolling beyond some configured "high" speed,
|
|
/// such as when programmatically scrolling to the top or a really fast user fling
|
|
final bool recommendDeferredLoading;
|
|
|
|
const TimelineState({this.isScrolling = false, this.recommendDeferredLoading = false});
|
|
|
|
bool get isInteracting => isScrolling || recommendDeferredLoading;
|
|
|
|
@override
|
|
bool operator ==(covariant TimelineState other) {
|
|
return isScrolling == other.isScrolling && recommendDeferredLoading == other.recommendDeferredLoading;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => isScrolling.hashCode ^ recommendDeferredLoading.hashCode;
|
|
|
|
TimelineState copyWith({bool? isScrolling, bool? recommendDeferredLoading}) {
|
|
return TimelineState(
|
|
isScrolling: isScrolling ?? this.isScrolling,
|
|
recommendDeferredLoading: recommendDeferredLoading ?? this.recommendDeferredLoading,
|
|
);
|
|
}
|
|
}
|
|
|
|
class TimelineStateNotifier extends Notifier<TimelineState> {
|
|
void setScrolling(bool isScrolling) {
|
|
state = state.copyWith(isScrolling: isScrolling);
|
|
}
|
|
|
|
void setRecommendDeferredLoading(bool recommendDeferredLoading) {
|
|
state = state.copyWith(recommendDeferredLoading: recommendDeferredLoading);
|
|
}
|
|
|
|
@override
|
|
TimelineState build() => const TimelineState(isScrolling: false, recommendDeferredLoading: false);
|
|
}
|
|
|
|
// This provider watches the buckets from the timeline service & args and serves the segments.
|
|
// It should be used only after the timeline service and timeline args provider is overridden
|
|
final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref) async* {
|
|
// maxHeight is left out on purpose, a height-only change must not restart the bucket stream
|
|
final (maxWidth, columnCount, spacing, groupByArg) = ref.watch(
|
|
timelineArgsProvider.select((args) => (args.maxWidth, args.columnCount, args.spacing, args.groupBy)),
|
|
);
|
|
final availableTileWidth = maxWidth - (spacing * (columnCount - 1));
|
|
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
|
|
|
final groupBy = groupByArg ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy));
|
|
|
|
final timelineService = ref.watch(timelineServiceProvider);
|
|
yield* timelineService.watchBuckets().map((buckets) {
|
|
return FixedSegmentBuilder(
|
|
buckets: buckets,
|
|
tileHeight: tileExtent,
|
|
columnCount: columnCount,
|
|
spacing: spacing,
|
|
groupBy: groupBy!,
|
|
).generate();
|
|
});
|
|
}, dependencies: [timelineServiceProvider, timelineArgsProvider]);
|
|
|
|
final timelineStateProvider = NotifierProvider<TimelineStateNotifier, TimelineState>(TimelineStateNotifier.new);
|