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; } @freezed abstract class TimelineState with _$TimelineState { const TimelineState._(); const factory TimelineState({@Default(false) bool isScrubbing, @Default(false) bool isScrolling}) = _TimelineState; bool get isInteracting => isScrubbing || isScrolling; } class TimelineStateNotifier extends Notifier { void setScrubbing(bool isScrubbing) { state = state.copyWith(isScrubbing: isScrubbing); } void setScrolling(bool isScrolling) { state = state.copyWith(isScrolling: isScrolling); } @override TimelineState build() => const TimelineState(isScrubbing: false, isScrolling: 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>((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.new);